From bf548aaeab0dfca9a88afaa0c5420270622f4a0d Mon Sep 17 00:00:00 2001 From: Thomas Fussell Date: Fri, 25 Mar 2016 14:45:06 +0800 Subject: [PATCH] initial commit --- .gitignore | 11 + .gitmodules | 4 + CMakeLists.txt | 95 +++++ LICENSE | 21 ++ README.md | 24 ++ docs/intro.md | 12 + package.json | 28 ++ resources/cort.png | Bin 0 -> 1026226 bytes source/brain/brain.cpp | 226 ++++++++++++ source/brain/brain.hpp | 81 +++++ source/brain/connection.cpp | 129 +++++++ source/brain/connection.hpp | 84 +++++ source/brain/emitter.cpp | 75 ++++ source/brain/emitter.hpp | 52 +++ source/brain/interface1.cpp | 38 ++ source/brain/interface1.hpp | 44 +++ source/brain/layer.cpp | 60 ++++ source/brain/layer.hpp | 78 ++++ source/brain/layer1.hpp | 36 ++ source/brain/layer2.cpp | 251 +++++++++++++ source/brain/layer2.hpp | 45 +++ source/brain/layer_factory.cpp | 61 ++++ source/brain/layer_factory.hpp | 38 ++ source/brain/neuron.cpp | 245 +++++++++++++ source/brain/neuron.hpp | 128 +++++++ source/brain/neurotransmitter.hpp | 44 +++ source/brain/receptor.cpp | 83 +++++ source/brain/receptor.hpp | 52 +++ source/brain/synapse.cpp | 197 +++++++++++ source/brain/synapse.hpp | 74 ++++ source/embind/cort_api.cpp | 82 +++++ source/embind/cort_api.hpp | 49 +++ source/interface/bci.cpp | 38 ++ source/interface/bci.hpp | 56 +++ source/interface/bci_reward_punishment.cpp | 75 ++++ source/interface/bci_reward_punishment.hpp | 44 +++ source/interface/bci_speech_from_file.cpp | 137 +++++++ source/interface/bci_speech_from_file.hpp | 54 +++ source/interface/script.cpp | 71 ++++ source/interface/script.hpp | 46 +++ source/math/quaternion.hpp | 90 +++++ source/math/vector3.hpp | 66 ++++ source/server/connection.cpp | 105 ++++++ source/server/connection.hpp | 79 +++++ source/server/connection_manager.cpp | 40 +++ source/server/connection_manager.hpp | 48 +++ source/server/header.hpp | 28 ++ source/server/main.cpp | 28 ++ source/server/mime_types.cpp | 46 +++ source/server/mime_types.hpp | 27 ++ source/server/reply.cpp | 255 ++++++++++++++ source/server/reply.hpp | 64 ++++ source/server/request.hpp | 35 ++ source/server/request_handler.cpp | 173 +++++++++ source/server/request_handler.hpp | 52 +++ source/server/request_parser.cpp | 333 ++++++++++++++++++ source/server/request_parser.hpp | 99 ++++++ source/server/server.cpp | 94 +++++ source/server/server.hpp | 67 ++++ source/simulation/entity.cpp | 39 ++ source/simulation/entity.hpp | 51 +++ source/simulation/random_number_generator.cpp | 133 +++++++ source/simulation/random_number_generator.hpp | 51 +++ source/simulation/simulation.cpp | 109 ++++++ source/simulation/simulation.hpp | 95 +++++ source/simulation/timer.cpp | 88 +++++ source/simulation/timer.hpp | 56 +++ source/simulation/trainer.cpp | 331 +++++++++++++++++ source/simulation/trainer.hpp | 102 ++++++ viewer | 1 + 70 files changed, 5753 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/intro.md create mode 100644 package.json create mode 100644 resources/cort.png create mode 100644 source/brain/brain.cpp create mode 100644 source/brain/brain.hpp create mode 100644 source/brain/connection.cpp create mode 100644 source/brain/connection.hpp create mode 100644 source/brain/emitter.cpp create mode 100644 source/brain/emitter.hpp create mode 100644 source/brain/interface1.cpp create mode 100644 source/brain/interface1.hpp create mode 100644 source/brain/layer.cpp create mode 100644 source/brain/layer.hpp create mode 100644 source/brain/layer1.hpp create mode 100644 source/brain/layer2.cpp create mode 100644 source/brain/layer2.hpp create mode 100644 source/brain/layer_factory.cpp create mode 100644 source/brain/layer_factory.hpp create mode 100644 source/brain/neuron.cpp create mode 100644 source/brain/neuron.hpp create mode 100644 source/brain/neurotransmitter.hpp create mode 100644 source/brain/receptor.cpp create mode 100644 source/brain/receptor.hpp create mode 100644 source/brain/synapse.cpp create mode 100644 source/brain/synapse.hpp create mode 100644 source/embind/cort_api.cpp create mode 100644 source/embind/cort_api.hpp create mode 100644 source/interface/bci.cpp create mode 100644 source/interface/bci.hpp create mode 100644 source/interface/bci_reward_punishment.cpp create mode 100644 source/interface/bci_reward_punishment.hpp create mode 100644 source/interface/bci_speech_from_file.cpp create mode 100644 source/interface/bci_speech_from_file.hpp create mode 100644 source/interface/script.cpp create mode 100644 source/interface/script.hpp create mode 100644 source/math/quaternion.hpp create mode 100644 source/math/vector3.hpp create mode 100755 source/server/connection.cpp create mode 100755 source/server/connection.hpp create mode 100755 source/server/connection_manager.cpp create mode 100755 source/server/connection_manager.hpp create mode 100755 source/server/header.hpp create mode 100755 source/server/main.cpp create mode 100755 source/server/mime_types.cpp create mode 100755 source/server/mime_types.hpp create mode 100755 source/server/reply.cpp create mode 100755 source/server/reply.hpp create mode 100755 source/server/request.hpp create mode 100755 source/server/request_handler.cpp create mode 100755 source/server/request_handler.hpp create mode 100755 source/server/request_parser.cpp create mode 100755 source/server/request_parser.hpp create mode 100755 source/server/server.cpp create mode 100755 source/server/server.hpp create mode 100644 source/simulation/entity.cpp create mode 100644 source/simulation/entity.hpp create mode 100644 source/simulation/random_number_generator.cpp create mode 100644 source/simulation/random_number_generator.hpp create mode 100644 source/simulation/simulation.cpp create mode 100644 source/simulation/simulation.hpp create mode 100644 source/simulation/timer.cpp create mode 100644 source/simulation/timer.hpp create mode 100644 source/simulation/trainer.cpp create mode 100644 source/simulation/trainer.hpp create mode 160000 viewer diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b7298c --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*~ +#*# +.DS_Store +*.sdf +*.suo +*.opensdf +*.vcxproj.user +*.o +*.d +build*/ +third-party/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f2acda0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "viewer"] + path = viewer + url = git@github.com:tfussell/cort.git + branch = gh-pages diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6667e0e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.4.3) + +if(NOT DEFINED CMAKE_SUPPRESS_DEVELOPER_WARNINGS) + set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE INTERNAL "No dev warnings") +endif() + +option(DEBUG "Set to ON to for debug configuration" OFF) +option(ASMJS "Set to OFF to skip compiling ${PROJECT_NAME} as an emscripten library to be run in the browser" ON) +option(SERVER "Set to ON to compile a standalone executable which acts as a server" OFF) + +if(ASMJS) + set(CMAKE_CXX_COMPILER em++) + set(CMAKE_C_COMPILER emcc) + include_directories(SYSTEM /usr/local/include) + set(CMAKE_CXX_CREATE_STATIC_LIBRARY "em++ -o ") +endif() + + +project(cort) + + +if(ASMJS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-warn-absolute-paths") +endif() + +if(CMAKE_CONFIGURATION_TYPES) + if(DEBUG) + set(CMAKE_BUILD_TYPE "Debug") + else() + set(CMAKE_BUILD_TYPE "Release") + endif() +endif() + +if(APPLE) + execute_process(COMMAND "sw_vers -productVersion | awk -F'.' '{print $1\".\"$2}'" + OUTPUT_VARIABLE OSX_VERSION) + set(CMAKE_OSX_DEPLOYMENT_TARGET ${OSX_VERSION}) +endif(APPLE) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(PROJECT_VENDOR "Thomas Fussell") +set(PROJECT_CONTACT "thomas.fussell@gmail.com") +set(PROJECT_URL "https://github.com/tfussell/cort") +set(PROJECT_DESCRIPTION "A biological neural network simulation platform") + +set(PROJECT_VERSION_MAJOR "0") +set(PROJECT_VERSION_MINOR "9") +set(PROJECT_VERSION_PATCH "0") +set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") +set(PROJECT_VERSION_FULL "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + +set(LIBRARY_VERSION ${PROJECT_VERSION_FULL}) + +include_directories(source) + +file(GLOB BRAIN_SOURCES source/brain/*.*pp) +source_group(brain FILES ${BRAIN_SOURCES}) +file(GLOB INTERFACE_SOURCES source/interface/*.*pp) +source_group(interface FILES ${INTERFACE_SOURCES}) +file(GLOB MATH_SOURCES source/math/*.*pp) +source_group(math FILES ${MATH_SOURCES}) +file(GLOB SIMULATION_SOURCES source/simulation/*.*pp) +source_group(simulation FILES ${SIMULATION_SOURCES}) + +set(SOURCES ${BRAIN_SOURCES} ${INTERFACE_SOURCES} ${MATH_SOURCES} ${SIMULATION_SOURCES}) + +if(SERVER) + file(GLOB SERVER_SOURCES source/server/*.*pp) + source_group(server FILES ${SERVER_SOURCES}) + + add_executable(cort ${SOURCES} ${SERVER_SOURCES}) + target_link_libraries(cort boost_system) + + add_custom_command(TARGET cort + POST_BUILD + COMMAND cp cort ../viewer + COMMAND cd ../viewer && ./cort + DEPENDS cort) +else() + file(GLOB EMBIND_SOURCES source/embind/*.*pp) + source_group(embind FILES ${EMBIND_SOURCES}) + + add_library(cort.js STATIC ${SOURCES} ${EMBIND_SOURCES}) + + set_target_properties(cort.js PROPERTIES PREFIX "") + set_target_properties(cort.js PROPERTIES SUFFIX ".bc") + + add_custom_command(TARGET cort.js + POST_BUILD + COMMAND emcc cort.js.bc -o cort.js --bind + COMMAND cp cort.js ../viewer/js/cort.js + COMMAND cd ../viewer && python3 -m http.server + DEPENDS cort.js.bc) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cd13356 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2006-2014 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8dceea --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +
+==== +cort is a platform for simulating, exploring, evolving, and interacting with biologically-inspired neural networks. It is written in C++, but can be compiled into JavaScript using Emscripten for easy cross-platform deployment. + +To try cort, click on [this demo](http://tfussell.github.io/cort/demo). + +Build +===== +This repository includes the latest emscripten compiled version of cort in bin/cort.html. If you'd like to compile cort yourself, make sure you have a C++ compiler that supports C++11, an updated browser, and emscripten. + +1. Copy repository + + git clone https://github.com/tfussell/cort +2. Compile using premake and platform build system + + cd cort/build && ./build-emscripten.sh +3. Run/view the simulation + + open cort/bin/cort.html + +License +======= +cort is licenced under the MIT license. +cort uses a modified version of [nxxcxx's Neural-Network](https://github.com/nxxcxx/Neural-Network) for visualization which is in turn based on [three.js](http://threejs.org). Both of these are also licensed under the MIT license. diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 0000000..2d5992a --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,12 @@ +Structure +========= +cort uses a modified client-server model. The optional client-side allows for visualization of and interaction with the current simulation and runs in the browser. The server-side is a simulation written in C++ containing a brain model defined by a JSON file. +From highest to lowest level, the structure of the server-side looks like: +Simulation - A simulation holds zero or more entities. There is only one simulation per instance of Cort. The simulation proceeds in a series of steps. In each step, every entity is updated in an arbitrary order. Every step represents a predetermined amount of time. A simulation continues until the application exits. +Entity - An entity is anything that can be simulated indepedently of other entities. It receives its inputs from other entities and the environment (part of the Simulation) and then creates outputs which affect other entities or change the environment. Every entity is created based on a JSON file. +Brain(Entity) - A brain is a type of entity. A brain is composed of zero or more layers. +Layer - A layer is a set of neurons which are created according to a certain pattern. We know that there is insufficient information in the human DNA to determine the type, position, behavior, etc. of every neuron so neurons must be created based on a generative algorithm. In general, a Layer is categorized by its dimension: 1D, 2D, or 3D. +Neuron - A neuron is the funademntal unit of compuation. Updating a neuron occurs in two steps. First, every neuron updates its membrane potential based on signals that were transmitted by presynaptic neurons and subsequently opened membrane receptors. +Connection +Synapse +Neurotransmitter diff --git a/package.json b/package.json new file mode 100644 index 0000000..9e6f4a2 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "cort", + "version": "0.1.0", + "description": "a platform for simulating and interacting with neural networks", + "main": "index.js", + "directories": { + "doc": "docs" + }, + "scripts": { + "test": "make -C build/gmake test" + }, + "repository": { + "type": "git", + "url": "https://github.com/tfussell/cort" + }, + "keywords": [ + "bnn", + "ann", + "neural", + "threejs" + ], + "author": "Thomas Fussell (https://thomas.fussell.io)", + "license": "MIT", + "bugs": { + "url": "https://github.com/tfussell/cort/issues" + }, + "homepage": "https://github.com/tfussell/cort" +} diff --git a/resources/cort.png b/resources/cort.png new file mode 100644 index 0000000000000000000000000000000000000000..c91e63e855dc811a5ad107d066c3983821672e48 GIT binary patch literal 1026226 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�EE@}oc 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 literal 0 HcmV?d00001 diff --git a/source/brain/brain.cpp b/source/brain/brain.cpp new file mode 100644 index 0000000..5f4cc68 --- /dev/null +++ b/source/brain/brain.cpp @@ -0,0 +1,226 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include + +#include "brain/brain.hpp" +#include "brain/connection.hpp" +#include "brain/layer_factory.hpp" +#include "simulation/timer.hpp" + +namespace cort { + +brain::brain() : + current_time_(0) +{ +} + +brain::~brain() +{ +} + +void brain::load(const std::string &json_string) +{ + auto json = nlohmann::json::parse(json_string); + + for(auto &layer : json["layers"]) + { + add_layer(layer); + } + + for(auto &connection : json["connections"]) + { + add_connection(connection); + } +} + +void brain::add_layer(const nlohmann::json &settings) +{ + std::string name = settings["name"]; + layers_[name] = layer_factory::create_layer(settings); +} + +void brain::update(double time) +{ + current_time_ = time; + + for(auto &layer : layers_) + { + for(auto &neuron : *layer.second.get()) + { + neuron->update_inputs(current_time_); + } + } + + for(auto &layer : layers_) + { + for(auto &neuron : *layer.second.get()) + { + neuron->update(current_time_); + } + } +} + +void brain::reset() +{ + +} + +void brain::add_connection(const nlohmann::json &settings) +{ + std::string from = settings["from"]; + std::string to = settings["to"]; + get_layer(from)->connect_to(*get_layer(to), settings); +} + +std::string brain::serialize_structure() const +{ + auto structure = nlohmann::json::object(); + auto &neurons = structure["neurons"]; + neurons = nlohmann::json::object(); + + for(auto &layer : layers_) + { + for(auto &neuron : *layer.second.get()) + { + auto neuron_id = std::to_string(neuron->get_id()); + auto &neuron_obj = neurons[neuron_id] = nlohmann::json::object(); + auto position = neuron->get_world_position(); + neuron_obj["position"] = nlohmann::json::array({position.x, position.y, position.z}); + auto &connections = neuron_obj["connections"]; + connections = nlohmann::json::object(); + + for(auto connection : neuron->get_outputs()) + { + auto postsynaptic = connection->get_postsynaptic(); + auto postsynaptic_id = std::to_string(postsynaptic->get_id()); + auto &connection_obj = connections[postsynaptic_id]; + connection_obj = nlohmann::json::object(); + connection_obj["weight"] = 0; + } + } + } + + return structure.dump(); +} + +std::string brain::serialize_state() const +{ + auto state = nlohmann::json::object(); + auto &neurons = state["neurons"]; + neurons = nlohmann::json::object(); + + for(auto &layer : layers_) + { + for(auto &neuron : *layer.second.get()) + { + auto neuron_id = std::to_string(neuron->get_id()); + auto &neuron_obj = neurons[neuron_id] = nlohmann::json::object(); + neuron_obj["voltage"] = neuron->get_voltage(); + auto &signals = neuron_obj["signals"] = nlohmann::json::object(); + auto &new_signals = neuron_obj["new_signals"] = nlohmann::json::object(); + auto &deleted_signals = neuron_obj["deleted_signals"] = nlohmann::json::object(); + + for(auto connection : neuron->get_outputs()) + { + while(connection->has_new_signal()) + { + auto signal = connection->pop_new_signal(); + auto &signal_obj = new_signals[std::to_string(signal.first)] = nlohmann::json::object(); + signal_obj["target_id"] = (int)connection->get_postsynaptic()->get_id(); + signal_obj["type"] = signal.second.second == 0 ? "action_potential" : signal.second.second == 1 ? "backpropagation_up" : signal.second.second == 2 ? "backpropagation_down" : "teaching"; + } + + while(connection->has_deleted_signal()) + { + auto signal = connection->pop_deleted_signal(); + auto &signal_obj = deleted_signals[std::to_string(signal.first)] = nlohmann::json::object(); + signal_obj["target_id"] = (int)connection->get_postsynaptic()->get_id(); + } + + for(auto &signal : *connection) + { + auto &signal_obj = signals[std::to_string(signal.first)] = nlohmann::json::object(); + signal_obj["state"] = signal.second.first / connection->get_length(); + signal_obj["target_id"] = (int)connection->get_postsynaptic()->get_id(); + } + } + } + } + + return state.dump(); +} + +void brain::set_input_image(const std::string &image_string) +{ + for(auto &layer : *this) + { + if(!layer.second->has_class("image")) continue; + + std::stringstream ss(image_string); + int part; + + for(auto &neuron : *layer.second) + { + ss >> part; + + if(part > 50) + { + neuron->set_external_current(14); + } + else + { + neuron->set_external_current(0); + } + } + } +} + +void brain::train(const std::string &input_image, const std::string &expected_output) +{ + set_input_image(input_image); + + if(expected_output == "") return; + + for(auto &layer : *this) + { + if(!layer.second->has_class("trainable")) continue; + + for(auto &neuron : *layer.second.get()) + { + neuron->clear_class(); + + if(neuron->get_label() == expected_output) + { + neuron->add_class("expected_fire"); + } + else + { + neuron->add_class("expected_no_fire"); + } + } + } +} + +} // namespace cort diff --git a/source/brain/brain.hpp b/source/brain/brain.hpp new file mode 100644 index 0000000..7c7a82b --- /dev/null +++ b/source/brain/brain.hpp @@ -0,0 +1,81 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include + +#include "brain/neuron.hpp" +#include "brain/layer2.hpp" +#include "simulation/entity.hpp" +#include "simulation/trainer.hpp" + +namespace cort { + +/* + This class represents a simulated brain. It can be constructed programmatically or from a "geneotype" which encodes its function and structure. It holds an internal state which may be serialized/deserialized. It functions in discrete time increments based on external time signals. Effectively, it has three parts. The input layers provide afferent signals. The central part processes these signals and stores an internal state. The final part is the set of motor layers which translate efferent signals into actions. + */ +class brain : public entity +{ +public: + using layer_container = std::unordered_map>; + using iterator = layer_container::iterator; + using const_iterator = layer_container::const_iterator; + + brain(); + virtual ~brain(); + + iterator begin() { return layers_.begin(); } + const_iterator begin() const { return cbegin(); } + const_iterator cbegin() const { return layers_.cbegin(); } + iterator end() { return layers_.end(); } + const_iterator end() const { return cend(); } + const_iterator cend() const { return layers_.cend(); } + + void load(const std::string &json_string); + void add_layer(const nlohmann::json &settings); + void add_connection(const nlohmann::json &settings); + + void set_input_image(const std::string &image_string); + +// void PrintState(); + void reset() override; + void update(double time) override; + layer *get_layer(const std::string &name) override { return layers_.at(name).get(); } + trainer *get_trainer() { return trainer_; } + void set_trainer(trainer &trainer) { trainer_ = &trainer; } + std::string serialize(); + void deserialize(const std::string &str); + + std::string serialize_structure() const override; + std::string serialize_state() const override; + + void train(const std::string &input_image, const std::string &expected_output); + +private: + double current_time_; + layer_container layers_; + trainer *trainer_; +}; + +} // namespace cort diff --git a/source/brain/connection.cpp b/source/brain/connection.cpp new file mode 100644 index 0000000..289fba4 --- /dev/null +++ b/source/brain/connection.cpp @@ -0,0 +1,129 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "brain/connection.hpp" + +namespace cort { + +std::size_t connection::num_signals_ = 0; +const double connection::transmission_velocity_ = 0.1; + +connection::connection(neuron *presynaptic, neuron *postsynaptic, double receptor_current) : + length_(1), + presynaptic_(presynaptic), + postsynaptic_(postsynaptic), + synapse_(presynaptic, postsynaptic, receptor_current) +{ + if(presynaptic_ != nullptr) + { + presynaptic_->add_output(this); + } + + if(postsynaptic_ != nullptr) + { + postsynaptic_->add_input(this); + } + + if(presynaptic_ && postsynaptic_) + { + auto pre_pos = presynaptic_->get_world_position(); + auto post_pos = postsynaptic_->get_world_position(); + auto dist = (pre_pos - post_pos).length(); + length_ = dist; + } +} + +connection::~connection() {} + +bool connection::has_updates() const +{ + return !signals_.empty() || has_deleted_signal() || has_new_signal(); +} + +void connection::update(double time) +{ + auto iter = signals_.begin(); + bool activate = false; + + while(iter != signals_.end()) + { + auto &signal = *iter; + signal.second.first += transmission_velocity_; + + if(signal.second.first > length_) + { + if(signal.second.second == 0) + { + activate = true; + } + + iter = signals_.erase(iter); + + if(new_signals_.find(signal.first) != new_signals_.end()) + { + // signal hasn't been popped from new list since it was created, so we don't care if it was deleted + new_signals_.erase(signal.first); + } + else + { + deleted_signals_[signal.first] = signal.second; + } + } + else + { + iter++; + } + } + + if(activate) + { + synapse_.activate(); + } + + synapse_.update(time); +} + +void connection::send_signal(int type) +{ + signal_type signal = {0, type}; + signals_.push_front({next_signal(), signal}); + new_signals_[signals_.front().first] = signal; +} + +connection::signal_id_pair connection::pop_deleted_signal() +{ + auto front = *deleted_signals_.begin(); + deleted_signals_.erase(deleted_signals_.begin()); + + return front; +} + +connection::signal_id_pair connection::pop_new_signal() +{ + auto front = *new_signals_.begin(); + new_signals_.erase(new_signals_.begin()); + + return front; +} + +} // namespace cort diff --git a/source/brain/connection.hpp b/source/brain/connection.hpp new file mode 100644 index 0000000..bf42500 --- /dev/null +++ b/source/brain/connection.hpp @@ -0,0 +1,84 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include +#include +#include + +#include "neuron.hpp" +#include "synapse.hpp" + +namespace cort { + +class connection +{ +public: + using signal_type = std::pair; + using signal_id_pair = std::pair; + using signal_container = std::list; + using signal_assoc_container = std::unordered_map; + + static std::size_t next_signal() { return num_signals_++; } + + connection(neuron *presynaptic, neuron *postsynaptic, double receptor_current); + ~connection(); + + bool has_updates() const; + + double get_length() const { return length_; } + + void update(double time); + void send_signal(int type); + synapse &get_synapse() { return synapse_; } + + neuron *get_presynaptic() const { return presynaptic_; } + neuron *get_postsynaptic() const { return postsynaptic_; } + + bool has_deleted_signal() const { return !deleted_signals_.empty(); } + signal_id_pair pop_deleted_signal(); + bool has_new_signal() const { return !new_signals_.empty(); } + signal_id_pair pop_new_signal(); + + signal_container::iterator begin() { return signals_.begin(); } + signal_container::const_iterator begin() const { return signals_.cbegin(); } + signal_container::iterator end() { return signals_.end(); } + signal_container::const_iterator end() const { return signals_.cend(); } + +private: + static const double transmission_velocity_; + static std::size_t num_signals_; + + double length_; + signal_container signals_; + signal_assoc_container new_signals_; + signal_assoc_container deleted_signals_; + + neuron *presynaptic_; + neuron *postsynaptic_; + + synapse synapse_; +}; + +} // namespace cort diff --git a/source/brain/emitter.cpp b/source/brain/emitter.cpp new file mode 100644 index 0000000..a47fd62 --- /dev/null +++ b/source/brain/emitter.cpp @@ -0,0 +1,75 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "brain/emitter.hpp" + +namespace cort { + +emitter::emitter() : + open_(false), + duration_(0.0), + //type_(neurotransmitter_type::invalid), + time_last_opened_(-(std::numeric_limits::max)()), + //max_amount_(0.0), + //regen_rate_(0.0), + amount_released_(0) +{ + +} + +emitter::emitter(neurotransmitter_type /*type*/, double duration, double amountPerOpening, double /*maxAmount*/, double /*regenRate*/) : + open_(false), + duration_(duration), + //type_(type), + time_last_opened_(-(std::numeric_limits::max)()), + //max_amount_(maxAmount), + //regen_rate_(regenRate), + amount_released_(amountPerOpening) +{ + +} + +void emitter::open(double time) +{ + time_last_opened_ = time; +} + +void emitter::update(double time) +{ + if(time - time_last_opened_ >= duration_) + { + open_ = false; + } +} + +bool emitter::is_open() +{ + return open_; +} + +void emitter::change_weight(double amount) +{ + amount_released_ += amount; +} + +} // namespace cort diff --git a/source/brain/emitter.hpp b/source/brain/emitter.hpp new file mode 100644 index 0000000..977e589 --- /dev/null +++ b/source/brain/emitter.hpp @@ -0,0 +1,52 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +#include "brain/neurotransmitter.hpp" + +namespace cort { + +class emitter +{ +public: + emitter(); + emitter(neurotransmitter_type type, double duration, double amountPerOpening, double maxAmount, double regenRate); + + void open(double time); + void update(double time); + bool is_open(); + void change_weight(double amount); + +private: + bool open_; + double duration_; + //neurotransmitter_type type_; + double time_last_opened_; + //double max_amount_; + //double regen_rate_; + double amount_released_; +}; + +} // namespace cort diff --git a/source/brain/interface1.cpp b/source/brain/interface1.cpp new file mode 100644 index 0000000..e18997a --- /dev/null +++ b/source/brain/interface1.cpp @@ -0,0 +1,38 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "brain/interface1.hpp" + +namespace cort { + +interface1::interface1(layer1 &layer) : layer_(layer) +{ + +} + +interface1::~interface1() +{ + +} + +} // namespace cort diff --git a/source/brain/interface1.hpp b/source/brain/interface1.hpp new file mode 100644 index 0000000..849cbbd --- /dev/null +++ b/source/brain/interface1.hpp @@ -0,0 +1,44 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +namespace cort { + +class layer1; + +class interface1 +{ +public: + interface1(layer1 &layer); + ~interface1(); + +protected: + layer1 &get_layer() { return layer_; } + +private: + layer1 &layer_; +}; + +} // namespace cort diff --git a/source/brain/layer.cpp b/source/brain/layer.cpp new file mode 100644 index 0000000..d2041d9 --- /dev/null +++ b/source/brain/layer.cpp @@ -0,0 +1,60 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include + +#include "brain/layer.hpp" +#include "brain/neuron.hpp" +#include "brain/synapse.hpp" + +namespace cort { + +std::size_t layer::num_layers_ = 0; + +layer::layer() : + position_(0, 0, 0), + rotation_(1, 0, 0, 0), + id_(num_layers_++) +{ +} + +layer::~layer() +{ + +} + +void layer::add_neuron(std::unique_ptr n) +{ + neurons_.push_back(std::move(n)); +} + +void layer::oscillate(double period) +{ + for(auto &neuron : *this) + { + neuron->oscillate(period); + } +} + +} // namespace cort diff --git a/source/brain/layer.hpp b/source/brain/layer.hpp new file mode 100644 index 0000000..ff3f32d --- /dev/null +++ b/source/brain/layer.hpp @@ -0,0 +1,78 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include +#include + +#include "math/quaternion.hpp" +#include "math/vector3.hpp" + +namespace cort { + +class neuron; + +class layer +{ + public: + using neuron_container = std::vector>; + using iterator = neuron_container::iterator; + using const_iterator = neuron_container::const_iterator; + + layer(); + virtual ~layer() = 0; + + std::size_t size() const { return neurons_.size(); } + + iterator begin() { return neurons_.begin(); } + const_iterator begin() const { return cbegin(); } + const_iterator cbegin() const { return neurons_.cbegin(); } + iterator end() { return neurons_.end(); } + const_iterator end() const { return cend(); } + const_iterator cend() const { return neurons_.cend(); } + + virtual void connect_to(layer &target, const nlohmann::json &settings) = 0; + virtual void set_position(vector3 position) = 0; + virtual void set_rotation(const quaternion &rotation) = 0; + virtual std::string get_type() const = 0; + + void add_class(const std::string &_class) { class_.insert(_class); } + bool has_class(const std::string &_class) const { return class_.find(_class) != class_.end(); } + + void oscillate(double period); + +protected: + void add_neuron(std::unique_ptr neuron); + + vector3 position_; + quaternion rotation_; + +private: + static std::size_t num_layers_; + std::size_t id_; + neuron_container neurons_; + std::unordered_set class_; +}; + +} // namespace cort diff --git a/source/brain/layer1.hpp b/source/brain/layer1.hpp new file mode 100644 index 0000000..3a21a1d --- /dev/null +++ b/source/brain/layer1.hpp @@ -0,0 +1,36 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include "brain/layer.hpp" + +namespace cort { + +class layer1 : layer +{ +public: + layer1(const rana::value &settings); + virtual ~layer1(); +}; + +} // namespace cort diff --git a/source/brain/layer2.cpp b/source/brain/layer2.cpp new file mode 100644 index 0000000..cc4482c --- /dev/null +++ b/source/brain/layer2.cpp @@ -0,0 +1,251 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include +#include +#include +#include +#include +#include + +#include "brain/layer2.hpp" +#include "brain/connection.hpp" +#include "brain/neuron.hpp" + +namespace { + +std::vector split_string(const std::string &to_split, char delim) +{ + std::size_t index = to_split.find(delim); + std::size_t prev = 0; + std::vector split; + + while(index != std::string::npos) + { + split.push_back(to_split.substr(prev, index - prev)); + prev = index + 1; // skip delim + index = to_split.find(delim, prev); + } + + split.push_back(to_split.substr(prev)); + + return split; +} + +} // namespace + +namespace cort { + +layer2::layer2(const nlohmann::json &settings) +{ + int width = settings["width"]; + int length = settings["length"]; + + for(int i = 0; i < width; i++) + { + for(int j = 0; j < length; j++) + { + auto n = std::unique_ptr(new neuron()); + n->set_local_position(vector3(0.5 + i - width / 2.0, 0, 0.5 + j - length / 2.0)); + add_neuron(std::move(n)); + } + } + + auto center = settings["center"]; + vector3 pos(center[0], center[1], center[2]); + set_position(pos); + + if(settings.find("orientation") != settings.end()) + { + auto orientation = settings["orientation"]; + auto deg2rad = [](double degrees) + { + // mod for floating point + // TODO: this can be more efficient but I don't feel like thinking about it right now + int rotations = std::abs(static_cast(degrees)) / 360 + (degrees < 0 ? 1 : 0); + degrees += 360 * rotations; + return degrees / 180.0 * 3.141592653589793238462643383279; + }; + auto rotation = quaternion::from_yaw_pitch_roll(deg2rad(orientation[0]), + deg2rad(orientation[1]), + deg2rad(orientation[2])); + set_rotation(rotation); + } +} + +layer2::~layer2() +{ + +} + +void layer2::connect_to(layer &target, const nlohmann::json &settings) +{ + if(target.get_type() != "layer2") + { + throw std::runtime_error("not implemented"); + } + + double current_change = 10; + + if(settings.find("current-change") != settings.end()) + { + current_change = settings["current-change"]; + } + + if(settings["type"] == "full") + { + for(auto &source_neuron : *this) + { + for(auto &target_neuron : target) + { + new connection(source_neuron.get(), target_neuron.get(), current_change); + } + } + } + else if(settings["type"] == "projection") + { + double source_density = settings["source-density"]; + double target_density = settings["target-density"]; + + double min_distance = 0; + if(settings.find("minimum-distance") != settings.end()) + { + min_distance = settings["minimum-distance"]; + } + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution source_dist(0.0,1.0); + std::poisson_distribution<> target_dist(target_density); + + auto source_region_strings = split_string(settings["source-region"], ','); + auto source_x0 = std::stod(source_region_strings[0]); + auto source_y0 = std::stod(source_region_strings[1]); + auto source_x1 = std::stod(source_region_strings[2]); + auto source_y1 = std::stod(source_region_strings[3]); + + double source_width = source_x1 - source_x0; + double source_height = source_y1 - source_y0; + + auto target_region_strings = split_string(settings["target-region"], ','); + auto target_x0 = std::stod(target_region_strings[0]); + auto target_y0 = std::stod(target_region_strings[1]); + auto target_x1 = std::stod(target_region_strings[2]); + auto target_y1 = std::stod(target_region_strings[3]); + + double target_width = target_x1 - target_x0; + double target_height = target_y1 - target_y0; + + double x_scale = target_width / source_width; + double y_scale = target_height / source_height; + + for(auto &source_neuron : *this) + { + if(source_dist(gen) > source_density) + { + continue; + } + + auto source_pos = source_neuron->get_local_position(); + + // use z here because local coordinates are in the xz plane, not xy + if(source_pos.x >= source_x0 + && source_pos.z >= source_y0 + && source_pos.x <= source_x1 + && source_pos.z <= source_y1) + { + auto dest_x = target_x0 + (source_pos.x - source_x0) * x_scale; + auto dest_y = target_y0 + (source_pos.z - source_y0) * y_scale; + + std::unordered_set connected; + auto connections = static_cast(target_density);//target_dist(gen)); + + while(connected.size() < connections) + { + std::pair closest = {nullptr, -1}; + + for(auto &target_neuron : target) + { + if(target_neuron.get() == source_neuron.get()) continue; + + auto target_pos = target_neuron->get_local_position(); + auto delta_x = dest_x - target_pos.x; + auto delta_y = dest_y - target_pos.z; + auto distance = std::sqrt(delta_x * delta_x + delta_y * delta_y); + + if(connected.find(target_neuron.get()) == connected.end() + && (closest.second == -1 || closest.second > distance) + && distance >= min_distance) + { + closest = {target_neuron.get(), distance}; + } + } + + if(closest.first != nullptr) + { + new connection(source_neuron.get(), closest.first, current_change); + connected.insert(closest.first); + } + else + { + if(connected.empty()) + { + std::printf("no connections"); + std::cerr << "warning: no neurons found in target region" << std::endl; + } + + break; + } + } + } + } + } +} + +void layer2::set_position(vector3 position) +{ + position_ = position; + + for(auto &neuron : *this) + { + neuron->update_world_position(position_, rotation_); + } +} + +std::string layer2::get_type() const +{ + return "layer2"; +} + +void layer2::set_rotation(const quaternion &rotation) +{ + rotation_ = rotation; + + for(auto &neuron : *this) + { + neuron->update_world_position(position_, rotation_); + } +} + +} // namespace cort diff --git a/source/brain/layer2.hpp b/source/brain/layer2.hpp new file mode 100644 index 0000000..5bcb5df --- /dev/null +++ b/source/brain/layer2.hpp @@ -0,0 +1,45 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +#include "brain/layer.hpp" + +namespace cort { + +class beuron; + +class layer2 : public layer +{ + public: + layer2(const nlohmann::json &settings); + virtual ~layer2(); + + /*virtual*/ void connect_to(layer &target, const nlohmann::json &settings); + /*virtual*/ void set_position(vector3 position); + /*virtual*/ void set_rotation(const quaternion &rotation); + /*virtual*/ std::string get_type() const; +}; + +} // namespace cort diff --git a/source/brain/layer_factory.cpp b/source/brain/layer_factory.cpp new file mode 100644 index 0000000..b88db71 --- /dev/null +++ b/source/brain/layer_factory.cpp @@ -0,0 +1,61 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "brain/layer_factory.hpp" +#include "brain/layer2.hpp" +#include "brain/neuron.hpp" + +namespace cort { + +std::unique_ptr layer_factory::create_layer(const nlohmann::json &settings) +{ + std::unique_ptr layer_ptr(new layer2(settings)); + + if(settings.find("class") != settings.end()) + { + for(auto &_class : settings.at("class")) + { + layer_ptr->add_class(_class); + + if(_class == "oscillator") + { + auto period = settings["oscillation-period"]; + layer_ptr->oscillate(period); + } + else if(_class == "labeled") + { + auto labels = settings["neuron-labels"]; + auto label_iter = labels.begin(); + + for(auto &neuron : *layer_ptr.get()) + { + neuron->set_label(*label_iter++); + } + } + } + } + + return std::move(layer_ptr); +} + +} // namespace cort diff --git a/source/brain/layer_factory.hpp b/source/brain/layer_factory.hpp new file mode 100644 index 0000000..dd3c0f3 --- /dev/null +++ b/source/brain/layer_factory.hpp @@ -0,0 +1,38 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include + +namespace cort { + +class layer; + +class layer_factory +{ +public: + static std::unique_ptr create_layer(const nlohmann::json &settings); +}; + +} // namespace cort diff --git a/source/brain/neuron.cpp b/source/brain/neuron.cpp new file mode 100644 index 0000000..a58fe23 --- /dev/null +++ b/source/brain/neuron.cpp @@ -0,0 +1,245 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include + +#include "brain/neuron.hpp" +#include "brain/connection.hpp" +#include "simulation/random_number_generator.hpp" + +namespace cort { + +/* + * Translated from: http://www.afodor.net/HHModel.htm + */ + +std::size_t neuron::num_neurons_ = 0; + +neuron::neuron() : + id_(num_neurons_++), + current_time_(0), + input_current_(0), + backpropagation_time_(0), + cm_(1.0), //Membrane capaciatance in uF/cm^2 + v_(-70.0), //Initial membrane potential + percent_na_channels_(100.0), + percent_k_channels_(100.0), + gna_(percent_na_channels_ * 120 / 100), //Maximum conductances in mS/cm^2 + gk_(percent_k_channels_ * 36 / 100), + gl_(0.3), + vna_(45.0), //Nernst reversal potentials in mV + vk_(-82.0), + vl_(-59.387), + activation_voltage_(-20.0), + firing_(false), + backpropagating_(false), + last_fired_(-100), + external_current_(0), + is_oscillator_(false), + oscillation_period_(0) +{ + double alphaH = 0.07 * std::exp(-1 * (v_ + 70.0) / 20.0); + double betaH = 1.0 / (1.0 + std::exp(-1 * (v_ + 40.0) / 10.0)); + h_ = alphaH / (alphaH + betaH); + + double alphaM = 0.1 * (v_ + 45.0) / (1.0 - std::exp(-1 * (v_ + 45.0) / 10.0)); + double betaM = 4.0 * std::exp(-1 * (v_ + 70.0) / 18.0); + m_ = alphaM / (alphaM + betaM); + + double alphaN = 0.01 * (v_ + 60) / (1.0 - std::exp(-1 * (v_ + 60.0) / 10.0)); + double betaN = 0.125 * std::exp(-1 * (v_ + 70.0) / 80.0); + n_ = alphaN / (alphaN + betaN); + + received_neurotransmitters_.push_back(neurotransmitter_type::glutamate); + emitted_neurotransmitters_.push_back(neurotransmitter_type::glutamate); +} + +neuron::~neuron() +{ + +} + +void neuron::update(double time) +{ + double last_time = current_time_; + + current_time_ = time; + delta_time_ = current_time_ - last_time; + + if(is_oscillator_) + { + if(!firing_ && current_time_ - last_fired_ > oscillation_period_) + { + on_action_potential_begin(); + } + else if(firing_) + { + on_action_potential_end(); + } + + return; + } + + double alphaH = 0.07 * std::exp(-1 * (v_ + 70.0) / 20.0); + double betaH = 1.0 / (1.0 + std::exp(-1 * (v_ + 40.0) / 10.0)); + double deltaH = (alphaH * (1 - h_) - betaH * h_) * delta_time_ * 1000; + + double alphaM = 0.1 * (v_ + 45.0) / (1.0 - std::exp(-1 * (v_ + 45.0) / 10.0)); + double betaM = 4.0 * std::exp(-1 * (v_ + 70.0) / 18.0); + double deltaM = (alphaM * (1 - m_) - betaM * m_) * delta_time_ * 1000; + + double alphaN = 0.01 * (v_ + 60) / (1.0 - std::exp(-1 * (v_ + 60.0) / 10.0)); + double betaN = 0.125 * std::exp(-1 * (v_ + 70.0) / 80.0); + double deltaN = (alphaN * (1 - n_) - betaN * n_) * delta_time_ * 1000; + + double naCurrent = gna_ * (v_ - vna_) * m_ * m_ * m_ * h_; + double kCurrent = gk_ * (v_ - vk_) * n_ * n_ * n_ * n_; + double lCurrent = gl_ * (v_ - vl_); + + double deltaV = ((input_current_ - kCurrent - naCurrent - lCurrent) / cm_) * delta_time_ * 1000; + + v_ += deltaV; + h_ += deltaH; + m_ += deltaM; + n_ += deltaN; + + if(!firing_ && v_ >= activation_voltage_) + { + on_action_potential_begin(); + } + else if(firing_ && v_ < activation_voltage_) + { + on_action_potential_end(); + } + + if(backpropagating_ && current_time_ - backpropagation_time_ > 0.001) + { + backpropagating_ = false; + } +} + +void neuron::on_action_potential_begin() +{ + firing_ = true; + last_fired_ = current_time_; + + for(auto connection : outputs_) + { + connection->send_signal(0); + } + + if(has_class("expected_fire") || has_class("expected_no_fire")) + { + std::cout << get_label() << " " << has_class("expected_fire") << " " << has_class("expected_no_fire") << std::endl; + + for(auto in_connection : inputs_) + { + for(auto sibling_connection : in_connection->get_presynaptic()->outputs_) + { + sibling_connection->get_postsynaptic()->backpropagate(); + } + } + } +} + +void neuron::on_action_potential_end() +{ + firing_ = false; +} + +void neuron::backpropagate() +{ + if(backpropagating_) return; + + bool fired_recently = std::abs(current_time_ - last_fired_) < 0.003; + bool up = false; + bool down = false; + + if(has_class("expected_fire") && !fired_recently) + { + std::cout << get_label() << " should have fired, didn't " << current_time_ << " " << last_fired_ << " " << (current_time_ - last_fired_) << std::endl; + up = true; + } + else if(has_class("expected_no_fire") && fired_recently) + { + std::cout << get_label() << " shouldn't have fired, did " << current_time_ << " " << last_fired_ << " " << (current_time_ - last_fired_) << std::endl; + down = true; + } + + if(!up && !down) + { + std::cout << get_label() << " no change" << current_time_ << " " << last_fired_ << " " << (current_time_ - last_fired_) << std::endl; + return; + } + + backpropagating_ = true; + backpropagation_time_ = current_time_; + + for(auto connection : inputs_) + { + if(connection->get_presynaptic()->has_class("collector")) continue; + + if(current_time_ - connection->get_presynaptic()->last_fired_ < 0.005) + { + if(up) + { + connection->get_synapse().backpropagate_up(); + connection->send_signal(1); + } + else + { + connection->get_synapse().backpropagate_down(); + connection->send_signal(2); + } + } + } +} + +void neuron::add_input(connection *conn) +{ + inputs_.push_back(conn); +} + +void neuron::add_output(connection *conn) +{ + outputs_.push_back(conn); +} + +void neuron::update_inputs(double time) +{ + input_current_ = external_current_; + + for(auto connection : inputs_) + { + connection->update(time); + input_current_ += connection->get_synapse().get_total_current(); + } +} + +void neuron::update_world_position(vector3 parent_position, const quaternion &parent_rotation) +{ + world_position_ = (parent_rotation * local_position_) + parent_position; +} + +} // namespace cort diff --git a/source/brain/neuron.hpp b/source/brain/neuron.hpp new file mode 100644 index 0000000..c3238a5 --- /dev/null +++ b/source/brain/neuron.hpp @@ -0,0 +1,128 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include + +#include "brain/neurotransmitter.hpp" +#include "brain/receptor.hpp" +#include "math/quaternion.hpp" +#include "math/vector3.hpp" + +namespace cort { + +class connection; + +class neuron +{ +public: + neuron(); + ~neuron(); + + std::size_t get_id() const { return id_; } + + void update(double time); + void augment_voltage(double amount); + void add_input(connection *conn); + void add_output(connection *conn); + void update_inputs(double time); + void activate_receptor(neurotransmitter_type neurotrans); + void backpropagate(); + + double get_last_fired() { return last_fired_; } + + std::vector get_emitted_neurotransmitters() { return emitted_neurotransmitters_; } + std::vector get_received_neurotransmitters() { return received_neurotransmitters_; } + + std::vector &get_inputs() { return inputs_; } + std::vector &get_outputs() { return outputs_; } + + void set_voltage(double v) { v_ = v; } + double get_voltage() { return v_; } + + vector3 get_local_position() const { return local_position_; } + void set_local_position(vector3 position) { local_position_ = position; } + void update_world_position(vector3 parent_position, const quaternion &parent_rotation); + vector3 get_world_position() const { return world_position_; } + + void set_external_current(double current) { external_current_ = current; } + + void oscillate(double period) { is_oscillator_ = true; oscillation_period_ = period; } + + void add_class(const std::string &to_add) { class_.insert(to_add); } + + void clear_class() { class_.clear(); } + + bool has_class(const std::string &to_check) { return class_.count(to_check) == 1; } + + void set_label(const std::string &label) { label_ = label; } + + std::string get_label() const { return label_; } + +private: + friend class bci_speech_from_file; + + static std::size_t num_neurons_; + + void on_action_potential_begin(); + void on_action_potential_end(); + + std::size_t id_; + + std::vector emitted_neurotransmitters_; + std::vector received_neurotransmitters_; + + double current_time_; + double delta_time_; + double last_time_; + double backpropagation_time_; + + std::vector inputs_; + std::vector outputs_; + + double input_current_; + double cm_; + double v_; + double percent_na_channels_; + double percent_k_channels_; + double gna_, gk_, gl_; + double vna_, vk_, vl_; + double n_, m_, h_; + double activation_voltage_; + bool firing_; + bool backpropagating_; + double last_fired_; + double external_current_; + + bool is_oscillator_; + double oscillation_period_; + + vector3 local_position_; + vector3 world_position_; + + std::unordered_set class_; + std::string label_; +}; + +} // namespace cort diff --git a/source/brain/neurotransmitter.hpp b/source/brain/neurotransmitter.hpp new file mode 100644 index 0000000..c4b11dc --- /dev/null +++ b/source/brain/neurotransmitter.hpp @@ -0,0 +1,44 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once + +namespace cort { + +enum neurotransmitter_type +{ + invalid, + glutamate, + gaba, + seratonin, + dopamine, + glycine, + aspartate, + epinephrine, + norepinephrine, + histamine, + acetylecholhine, + adenosine +}; + +} // namespace cort diff --git a/source/brain/receptor.cpp b/source/brain/receptor.cpp new file mode 100644 index 0000000..bfe348a --- /dev/null +++ b/source/brain/receptor.cpp @@ -0,0 +1,83 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include + +#include "brain/receptor.hpp" + +namespace cort { + +receptor::receptor() : + open_(false), + duration_(0), + current_change_per_molecule_(0), + time_last_opened_(-(std::numeric_limits::max)()) +{ + +} + +receptor::receptor(neurotransmitter_type /*type*/, double duration, double currentChange) : + open_(false), + duration_(duration), + current_change_per_molecule_(currentChange), + time_last_opened_(-1 * (duration + 0.01)) //can't be zero as it would make it appear to be open, can't be very negative as that would be large delta-time +{ + +} + +receptor::~receptor() +{ + +} + +void receptor::open(double currentTime) +{ + time_last_opened_ = currentTime; + open_ = true; +} + +void receptor::update(double currentTime) +{ + if(open_ && currentTime - time_last_opened_ >= duration_) + { + open_ = false; + } +} + +bool receptor::is_open() +{ + return open_; +} + +double receptor::get_current_change() +{ + return current_change_per_molecule_; +} + +void receptor::change_weight(double amount) +{ + std::cout << "receptor changed from " << current_change_per_molecule_ << " to " << current_change_per_molecule_ + amount << std::endl; + current_change_per_molecule_ += amount; +} + +} // namespace cort diff --git a/source/brain/receptor.hpp b/source/brain/receptor.hpp new file mode 100644 index 0000000..bb728ba --- /dev/null +++ b/source/brain/receptor.hpp @@ -0,0 +1,52 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +#include "brain/Neurotransmitter.hpp" + +namespace cort { + +class receptor +{ +public: + receptor(); + receptor(neurotransmitter_type type, double duration, double currentChange); + ~receptor(); + + void open(double currentTime); + void update(double currentTime); + bool is_open(); + double get_current_change(); + void change_weight(double amount); + +private: + bool open_; + double duration_; + //NeurotransmitterType m_Type; + double current_change_per_molecule_; + double time_last_opened_; +}; + +} // namespace cort diff --git a/source/brain/synapse.cpp b/source/brain/synapse.cpp new file mode 100644 index 0000000..3620031 --- /dev/null +++ b/source/brain/synapse.cpp @@ -0,0 +1,197 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include + +#include "brain/synapse.hpp" +#include "brain/neuron.hpp" +#include "simulation/random_number_generator.hpp" + +namespace { + +template +std::vector intersect(const std::vector &left, const std::vector &right) +{ + std::vector intersection; + + for(const auto &i : left) + { + if(std::find(right.begin(), right.end(), i) != right.end()) + { + intersection.push_back(i); + } + } + + return intersection; +} + +} + +namespace cort { + +synapse::synapse(neuron *pre, neuron *post, double receptor_current) : + current_time_(0), + delta_time_(1), + activation_duration_(0.001), + time_last_activated_(0), + presynaptic_(pre), + postsynaptic_(post), + hebbian_(true) +{ + double duration = 0.001; + + if(pre != nullptr && post == nullptr) + { + for(auto nt : pre->get_emitted_neurotransmitters()) + { + emitters_[nt] = emitter(nt, 0.1, 1.0, 10.0, 2.0); + } + } + + if(post != nullptr && pre == nullptr) + { + for(auto nt : post->get_received_neurotransmitters()) + { + receptors_[nt] = receptor(nt, duration, receptor_current); + } + } + + // ignore signals that are emitted but not received and vice versa + if(post != nullptr && pre != nullptr) + { + auto intersection = intersect(pre->get_emitted_neurotransmitters(), + post->get_received_neurotransmitters()); + + emitted_ = intersection; + received_ = intersection; + + for(auto nt : intersection) + { + emitters_[nt] = emitter(nt, 0.01, 1, 10.0, 2.0); + receptors_[nt] = receptor(nt, duration, receptor_current); + } + } +} + +synapse::~synapse() +{ +} + +void synapse::update(double time) +{ + double lastTime = current_time_; + current_time_ = time; + delta_time_ = current_time_ - lastTime; + + for(std::size_t i = 0; i < emitted_.size(); i++) + { + emitters_[emitted_[i]].update(time); + } + + for(std::size_t i = 0; i < received_.size(); i++) + { + receptors_[received_[i]].update(time); + } +} + +void synapse::activate() +{ + for(std::size_t i = 0; i < received_.size(); i++) + { + receptors_[received_[i]].open(current_time_); + } +} + +void synapse::backpropagate_up() +{ + if(!postsynaptic_ || !presynaptic_) + { + return; + } + + double timeDiff = postsynaptic_->get_last_fired() - presynaptic_->get_last_fired(); + double weightChange = 0.5;//0.8 * std::exp(-timeDiff); + + for(std::size_t i = 0; i < emitted_.size(); i++) + { + emitters_[emitted_[i]].change_weight(weightChange); + } + + for(std::size_t i = 0; i < received_.size(); i++) + { + receptors_[received_[i]].change_weight(weightChange); + } + + //presynaptic_->backpropagate_up(); +} + +void synapse::backpropagate_down() +{ + if(!postsynaptic_ || !presynaptic_) + { + return; + } + + double timeDiff = postsynaptic_->get_last_fired() - presynaptic_->get_last_fired(); + double weightChange = -0.5;//-0.5 * std::exp(-timeDiff); + + for(std::size_t i = 0; i < emitted_.size(); i++) + { + emitters_[emitted_[i]].change_weight(weightChange); + } + + for(std::size_t i = 0; i < received_.size(); i++) + { + receptors_[received_[i]].change_weight(weightChange); + } + + //presynaptic_->backpropagate_down(); +} + +/* +void Synapse::activate_receptor(NeurotransmitterType receptorType) +{ + if(m_Receptors.find(receptorType) != m_Receptors.end()) + { + m_Receptors[receptorType].Open(m_CurrentTime); + } +} +*/ + +double synapse::get_total_current() +{ + total_current_ = 0.; + + for(auto i = receptors_.begin(); i != receptors_.end(); i++) + { + if(i->second.is_open()) + { + total_current_ += i->second.get_current_change(); + } + } + + return total_current_; +} + +} // namespace cort diff --git a/source/brain/synapse.hpp b/source/brain/synapse.hpp new file mode 100644 index 0000000..6edde7f --- /dev/null +++ b/source/brain/synapse.hpp @@ -0,0 +1,74 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include +#include + +#include "brain/emitter.hpp" +#include "brain/neurotransmitter.hpp" +#include "brain/receptor.hpp" + +namespace cort { + +class neuron; + +class synapse +{ +public: + synapse(neuron *presynaptic, neuron *postsynaptic, double receptor_current); + ~synapse(); + + void activate(); + void update(double time); + double get_total_current(); + + void backpropagate_up(); + void backpropagate_down(); + + neuron *get_presynaptic_neuron() const { return presynaptic_; } + neuron *get_postsynaptic_neuron() const { return postsynaptic_; } + +private: + friend class bci_speech_from_file; + + std::vector emitted_; + std::vector received_; + + std::map emitters_; + std::map receptors_; + + double current_time_; + double delta_time_; + double activation_duration_; + double time_last_activated_; + double total_current_; + + neuron *presynaptic_; + neuron *postsynaptic_; + + bool hebbian_; +}; + +} // namespace cort diff --git a/source/embind/cort_api.cpp b/source/embind/cort_api.cpp new file mode 100644 index 0000000..f987368 --- /dev/null +++ b/source/embind/cort_api.cpp @@ -0,0 +1,82 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include + +#include "embind/cort_api.hpp" +#include "brain/connection.hpp" + +using namespace emscripten; + +cort_api::cort_api() : + simulation_(2000, 50000000, 100000) +{ + simulation_.add_entity(brain_); +} + +void cort_api::load_script(std::string contents) +{ + brain_.load(contents); +} + +std::string cort_api::get_structure() +{ + return brain_.serialize_structure(); +} + +std::string cort_api::get_state() +{ + return brain_.serialize_state(); +} + +void cort_api::step() +{ + simulation_.step(); +} + +double cort_api::get_sim_time() +{ + return simulation_.get_total_sim_time(); +} + +void cort_api::set_input_image(std::string image_string) +{ + brain_.set_input_image(image_string); +} + +void cort_api::train(std::string image_string, std::string expected_output) +{ + brain_.train(image_string, expected_output); +} + +EMSCRIPTEN_BINDINGS(cort_bindings) { + class_("cort_api") + .constructor<>() + .function("load_script", &cort_api::load_script) + .function("step", &cort_api::step) + .function("set_input_image", &cort_api::set_input_image) + .function("train", &cort_api::train) + .function("get_sim_time", &cort_api::get_sim_time) + .function("get_structure", &cort_api::get_structure) + .function("get_state", &cort_api::get_state); +} diff --git a/source/embind/cort_api.hpp b/source/embind/cort_api.hpp new file mode 100644 index 0000000..ff0e2bf --- /dev/null +++ b/source/embind/cort_api.hpp @@ -0,0 +1,49 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include + +#include "simulation/simulation.hpp" + +class cort_api +{ +public: + cort_api(); + + void load_script(std::string contents); + + std::string get_structure(); + std::string get_state(); + + void step(); + + double get_sim_time(); + + void set_input_image(std::string image_string); + + void train(std::string image_string, std::string expected_output); + +private: + cort::simulation simulation_; + cort::brain brain_; +}; diff --git a/source/interface/bci.cpp b/source/interface/bci.cpp new file mode 100644 index 0000000..4a4d7b8 --- /dev/null +++ b/source/interface/bci.cpp @@ -0,0 +1,38 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "interface/bci.hpp" + +namespace cort { + +bci::bci() +{ + +} + +bci::~bci() +{ + +} + +} // namespace cort diff --git a/source/interface/bci.hpp b/source/interface/bci.hpp new file mode 100644 index 0000000..162a8e6 --- /dev/null +++ b/source/interface/bci.hpp @@ -0,0 +1,56 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +namespace cort { + +class layer; + +enum class bci_type +{ + input, + output +}; + +class bci +{ +public: + bci(); + virtual ~bci(); + + void set_layer(layer *layer) { layer_ = layer; }; + layer *get_layer() const { return layer_; }; + + virtual void initialize() = 0; + + virtual std::string get_state() = 0; + virtual void set_state(const std::string &state) = 0; + +private: + layer *layer_; +}; + +} // namespace cort + diff --git a/source/interface/bci_reward_punishment.cpp b/source/interface/bci_reward_punishment.cpp new file mode 100644 index 0000000..f248733 --- /dev/null +++ b/source/interface/bci_reward_punishment.cpp @@ -0,0 +1,75 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include + +#include "interface/bci_reward_punishment.hpp" +#include "brain/layer.hpp" +#include "brain/neurotransmitter.hpp" +#include "brain/neuron.hpp" + +namespace cort { + +bci_reward_punishment::bci_reward_punishment(layer *layer) +{ + set_layer(layer); + initialize(); +} + +bci_reward_punishment::~bci_reward_punishment() +{ +} + +void bci_reward_punishment::initialize() +{ + +} + +std::string bci_reward_punishment::get_state() +{ + return ""; +} + +void bci_reward_punishment::set_state(const std::string &/*state*/) +{ +/* + std::printf("Reward: %s\n",state.c_str()); + + if(state == "Good") + { + for(unsigned int i = 0; i < m_Interface->GetSize(); i++) + { + m_Interface->GetNeuron(i)->BackpropagateUp(); + } + } + else if(state == "Bad") + { + for(unsigned int i = 0; i < m_Interface->GetSize(); i++) + { + m_Interface->GetNeuron(i)->BackpropagateDown(); + } + } +*/ +} + +} // namespace cort diff --git a/source/interface/bci_reward_punishment.hpp b/source/interface/bci_reward_punishment.hpp new file mode 100644 index 0000000..a692c8b --- /dev/null +++ b/source/interface/bci_reward_punishment.hpp @@ -0,0 +1,44 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include + +#include "interface/BCI.hpp" + +namespace cort { + +class bci_reward_punishment : public bci +{ +public: + bci_reward_punishment(layer *layer); + ~bci_reward_punishment(); + + /*virtual*/ void initialize(); + + /*virtual*/ std::string get_state(); + /*virtual*/ void set_state(const std::string &state); +}; + +} // namespace cort diff --git a/source/interface/bci_speech_from_file.cpp b/source/interface/bci_speech_from_file.cpp new file mode 100644 index 0000000..18f2123 --- /dev/null +++ b/source/interface/bci_speech_from_file.cpp @@ -0,0 +1,137 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include + +#include "interface/bci_speech_from_file.hpp" +#include "brain/neurotransmitter.hpp" +#include "brain/neuron.hpp" +#include "brain/layer.hpp" + +namespace cort { + +bci_speech_from_file::bci_speech_from_file(layer *layer, const std::string &filename) : + filename_(filename) +{ + set_layer(layer); + initialize(); +} + +bci_speech_from_file::~bci_speech_from_file() +{ + +} + +void bci_speech_from_file::initialize() +{ + load_script(filename_); +} + +void bci_speech_from_file::load_script(const std::string &/*filename*/) +{ +/* + std::fstream f("../scripts/" + filename); + + if(!f.is_open()) + { + std::printf("Error opening file: %s\n",filename.c_str()); + return; + } + + std::printf("Loading BCI commands from file: %s\n",filename.c_str()); + int numCommands = 0; + f >> numCommands; + int index; + std::string line; + int i = 0; + + while(i++ < numCommands && f.good()) + { + f >> index; + f >> line; + Neuron *neuron = m_Interface->GetNeuron(index); + + if(m_Type == BciType_Input) + { + m_SynapseCommandMap[line] = new Synapse(0, neuron); + } + else if(m_Type == BciType_Output) + { + m_SynapseCommandMap[line] = new Synapse(neuron, 0); + } + } + + f.close(); +*/ +} + +std::string bci_speech_from_file::get_state() +{ +/* + for(std::unordered_map::iterator i = m_SynapseCommandMap.begin(); i != m_SynapseCommandMap.end(); i++) + { + if(i->second->m_Presynaptic->m_LastFired == i->second->m_Presynaptic->m_CurrentTime) + { + return i->first; + } + } +*/ + return ""; +} + +void bci_speech_from_file::set_state(const std::string &/*state*/) +{ +/* + if(m_SynapseCommandMap.find(state) != m_SynapseCommandMap.end()) + { + std::printf("Set state: %s\n", state.c_str()); + m_SynapseCommandMap[state]->ActivateReceptor(NT_Glutamate); + } +*/ +} +/* +Neuron *bci_speech_from_file::GetNeuron(const std::string &state) +{ + Synapse *syn = 0; + + if(m_SynapseCommandMap.find(state) != m_SynapseCommandMap.end()) + { + syn = m_SynapseCommandMap[state]; + } + + if(syn && m_Type == BciType_Input) + { + return syn->m_Postsynaptic; + } + else if(syn && m_Type == BciType_Output) + { + return syn->m_Presynaptic; + } + else + { + return 0; + } +} +*/ + +} // namespace cort diff --git a/source/interface/bci_speech_from_file.hpp b/source/interface/bci_speech_from_file.hpp new file mode 100644 index 0000000..7fbc0ad --- /dev/null +++ b/source/interface/bci_speech_from_file.hpp @@ -0,0 +1,54 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include + +#include "interface/bci.hpp" + +namespace cort { + +class synapse; + +class bci_speech_from_file : public bci +{ +public: + bci_speech_from_file(layer *layer, const std::string &filename); + ~bci_speech_from_file(); + + void load_script(const std::string &filename); + + /*virtual*/ void initialize(); + + /*virtual*/ std::string get_state(); + /*virtual*/ void set_state(const std::string &state); + +// Neuron *GetNeuron(const std::string &state); + +private: + std::string filename_; + std::unordered_map synapse_command_map_; +}; + +} // namespace cort diff --git a/source/interface/script.cpp b/source/interface/script.cpp new file mode 100644 index 0000000..9e080b5 --- /dev/null +++ b/source/interface/script.cpp @@ -0,0 +1,71 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "interface/script.hpp" + +namespace cort { + +script::script(const std::string &filename) : + filename_(filename) +{ + std::string fullpath = "../scripts/" + filename_; + stream_.open(fullpath, std::ifstream::in); + + if(!stream_.is_open()) + { + throw std::runtime_error("Error opening file in __func__(__LINE__) in __FILE__: " + filename_); + } +} + +script::~script() +{ + stream_.close(); +} + +std::string script::get_next_string() +{ + std::string ret(""); + stream_ >> ret; + return ret; +} + +int script::get_next_int() +{ + int ret = 0; + stream_ >> ret; + return ret; +} + +double script::get_next_double() +{ + double ret = 0; + stream_ >> ret; + return ret; +} + +bool script::is_finished() +{ + return !stream_.good(); +} + +} // namespace cort diff --git a/source/interface/script.hpp b/source/interface/script.hpp new file mode 100644 index 0000000..ef94b69 --- /dev/null +++ b/source/interface/script.hpp @@ -0,0 +1,46 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include + +namespace cort { + +class script +{ +public: + script(const std::string &filename); + ~script(); + + bool is_finished(); + int get_next_int(); + std::string get_next_string(); + double get_next_double(); + +private: + std::string filename_; + std::ifstream stream_; +}; + +} // namespace cort diff --git a/source/math/quaternion.hpp b/source/math/quaternion.hpp new file mode 100644 index 0000000..265e19e --- /dev/null +++ b/source/math/quaternion.hpp @@ -0,0 +1,90 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once + +#include "vector3.hpp" + +namespace cort { + +class quaternion +{ +public: + static quaternion from_yaw_pitch_roll(const vector3 &v) + { + return from_yaw_pitch_roll(v.x, v.y, v.z); + } + + static quaternion from_yaw_pitch_roll(double yaw, double pitch, double roll) + { + double cy = std::cos(yaw / 2); + double sy = std::sin(yaw / 2); + double cr = std::cos(-roll / 2); + double sr = std::sin(-roll / 2); + double cp = std::cos(-pitch / 2); + double sp = std::sin(-pitch / 2); + + return quaternion(cr * cp * cy - sr * sp * sy, + cr * sp * cy - sr * cp * sy, + cr * cp * sy + sr * sp * cy, + sr * cp * cy + cr * sp * sy); + } + + quaternion(double w, double x, double y, double z) : w(w), x(x), y(y), z(z) {} + quaternion(double w, vector3 v) : w(w), x(v.x), y(v.y), z(v.z) {} + quaternion() : quaternion(0, 0, 0, 0) {} + quaternion(const quaternion &other) : quaternion(other.w, other.x, other.y, other.z) {} + + void normalize() { auto l = length(); if(l == 0) return; w /= l; x /= l; y /= l; z /= l; } + double length() const { return std::sqrt(w * w + x * x + y * y + z * z); } + + quaternion conjugate() const { return quaternion(w, vector3(x, y, z) * -1); } + + quaternion &operator=(quaternion other) { w = other.w; x = other.x; y = other.y; z = other.z; return *this; } + quaternion operator+(quaternion other) const { return quaternion(w + other.w, x + other.x, y + other.y, z + other.z); } + quaternion operator-(quaternion other) const { return quaternion(w - other.w, x - other.x, y - other.y, z - other.z); } + vector3 operator*(const vector3 &v) const { return (*this * quaternion(0, v) * conjugate()).vector_part(); } + quaternion operator*(quaternion other) const + { + auto v1 = vector_part(); + auto v2 = other.vector_part(); + return quaternion(w * other.w - v1.dot(v2), w * v2 + other.w * v1 + v1 * v2); + } + quaternion operator*(double scalar) const { return quaternion(w * scalar, x * scalar, y * scalar, z * scalar); } + quaternion operator/(double scalar) const { return quaternion(w / scalar, x / scalar, y / scalar, z / scalar); } + + vector3 vector_part() const { return vector3(x, y, z); } + + double w; + double x; + double y; + double z; +}; + +inline std::ostream &operator<<(std::ostream &stream, const quaternion &q) +{ + stream << "quaternion(" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << ")"; + return stream; +} + +} // namespace cort diff --git a/source/math/vector3.hpp b/source/math/vector3.hpp new file mode 100644 index 0000000..32d8d4b --- /dev/null +++ b/source/math/vector3.hpp @@ -0,0 +1,66 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include + +namespace cort { + +struct vector3 +{ + vector3(double x, double y, double z) : x(x), y(y), z(z) {} + vector3() : vector3(0, 0, 0) {} + vector3(const vector3 &other) : vector3(other.x, other.y, other.z) {} + + void normalize() { auto l = length(); if(l == 0) return; x /= l; y /= l; z /= l; } + double length() const { return std::sqrt(x * x + y * y + z * z); } + double dot(vector3 other) const { return x * other.x + y * other.y + z * other.z; } + + vector3 &operator=(vector3 other) { x = other.x; y = other.y; z = other.z; return *this; } + vector3 operator+(vector3 other) const { return vector3(x + other.x, y + other.y, z + other.z); } + vector3 operator-(vector3 other) const { return vector3(x - other.x, y - other.y, z - other.z); } + vector3 operator*(vector3 other) const { return vector3(y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x); } + vector3 operator*(double scalar) const { return vector3(x * scalar, y * scalar, z * scalar); } + vector3 operator/(double scalar) const { return vector3(x / scalar, y / scalar, z / scalar); } + vector3 &operator+=(vector3 other) { *this = *this + other; return *this; } + vector3 &operator-=(vector3 other) { *this = *this - other; return *this; } + vector3 &operator*=(vector3 other) { *this = *this * other; return *this; } + vector3 &operator*=(double scalar) { *this = *this * scalar; return *this; } + vector3 &operator/=(double scalar) { *this = *this * (1 / scalar); return *this; } + + double x; + double y; + double z; +}; + +inline vector3 operator*(double scalar, vector3 vector) { return vector * scalar; } +inline vector3 operator/(double scalar, vector3 vector) { return vector / scalar; } + +inline std::ostream &operator<<(std::ostream &stream, const vector3 &v) +{ + stream << "vector3(" << v.x << ", " << v.y << ", " << v.z << ")"; + return stream; +} + +} // namespace cort diff --git a/source/server/connection.cpp b/source/server/connection.cpp new file mode 100755 index 0000000..5e5a348 --- /dev/null +++ b/source/server/connection.cpp @@ -0,0 +1,105 @@ +// +// connection.cpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "connection.hpp" +#include +#include +#include +#include "connection_manager.hpp" +#include "request_handler.hpp" + +namespace http { +namespace server { + +connection::connection(boost::asio::ip::tcp::socket socket, + connection_manager& manager, request_handler& handler) + : socket_(std::move(socket)), + connection_manager_(manager), + request_handler_(handler) +{ +} + +void connection::start() +{ + do_read(); +} + +void connection::stop() +{ + socket_.close(); +} + +void connection::do_read() +{ + auto self(shared_from_this()); + socket_.async_read_some(boost::asio::buffer(buffer_), + [this, self](boost::system::error_code ec, std::size_t bytes_transferred) + { + if (!ec) + { + request_parser::result_type result; + + std::tie(result, std::ignore) = request_parser_.parse( + request_, buffer_.data(), buffer_.data() + bytes_transferred); + + auto t = std::time(0); + auto now = *std::localtime(&t); + + std::cout << "[" << now.tm_mday << "/" << now.tm_mon << "/" << now.tm_year << " "; + std::cout << now.tm_hour << ":" << now.tm_min << ":" << now.tm_sec << "] "; + std::cout << "\"" << request_.method << " " << request_.uri << " HTTP/" << request_.http_version_major << "." << request_.http_version_minor << "\" "; + + if (result == request_parser::good) + { + request_handler_.handle_request(request_, reply_); + std::cout << reply_.status << std::endl; + do_write(); + } + else if (result == request_parser::bad) + { + reply_ = reply::stock_reply(reply::bad_request); + std::cout << reply::bad_request << std::endl; + do_write(); + } + else + { + do_read(); + } + } + else if (ec != boost::asio::error::operation_aborted) + { + connection_manager_.stop(shared_from_this()); + } + }); +} + +void connection::do_write() +{ + auto self(shared_from_this()); + boost::asio::async_write(socket_, reply_.to_buffers(), + [this, self](boost::system::error_code ec, std::size_t) + { + if (!ec) + { + // Initiate graceful connection closure. + boost::system::error_code ignored_ec; + socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, + ignored_ec); + } + + if (ec != boost::asio::error::operation_aborted) + { + connection_manager_.stop(shared_from_this()); + } + }); +} + +} // namespace server +} // namespace http diff --git a/source/server/connection.hpp b/source/server/connection.hpp new file mode 100755 index 0000000..3c87b6a --- /dev/null +++ b/source/server/connection.hpp @@ -0,0 +1,79 @@ +// +// connection.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_CONNECTION_HPP +#define HTTP_CONNECTION_HPP + +#include +#include +#include +#include "reply.hpp" +#include "request.hpp" +#include "request_handler.hpp" +#include "request_parser.hpp" + +namespace http { +namespace server { + +class connection_manager; + +/// Represents a single connection from a client. +class connection + : public std::enable_shared_from_this +{ +public: + connection(const connection&) = delete; + connection& operator=(const connection&) = delete; + + /// Construct a connection with the given socket. + explicit connection(boost::asio::ip::tcp::socket socket, + connection_manager& manager, request_handler& handler); + + /// Start the first asynchronous operation for the connection. + void start(); + + /// Stop all asynchronous operations associated with the connection. + void stop(); + +private: + /// Perform an asynchronous read operation. + void do_read(); + + /// Perform an asynchronous write operation. + void do_write(); + + /// Socket for the connection. + boost::asio::ip::tcp::socket socket_; + + /// The manager for this connection. + connection_manager& connection_manager_; + + /// The handler used to process the incoming request. + request_handler& request_handler_; + + /// Buffer for incoming data. + std::array buffer_; + + /// The incoming request. + request request_; + + /// The parser for the incoming request. + request_parser request_parser_; + + /// The reply to be sent back to the client. + reply reply_; +}; + +typedef std::shared_ptr connection_ptr; + +} // namespace server +} // namespace http + +#endif // HTTP_CONNECTION_HPP diff --git a/source/server/connection_manager.cpp b/source/server/connection_manager.cpp new file mode 100755 index 0000000..5699ed1 --- /dev/null +++ b/source/server/connection_manager.cpp @@ -0,0 +1,40 @@ +// +// connection_manager.cpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "connection_manager.hpp" + +namespace http { +namespace server { + +connection_manager::connection_manager() +{ +} + +void connection_manager::start(connection_ptr c) +{ + connections_.insert(c); + c->start(); +} + +void connection_manager::stop(connection_ptr c) +{ + connections_.erase(c); + c->stop(); +} + +void connection_manager::stop_all() +{ + for (auto c: connections_) + c->stop(); + connections_.clear(); +} + +} // namespace server +} // namespace http diff --git a/source/server/connection_manager.hpp b/source/server/connection_manager.hpp new file mode 100755 index 0000000..400d14b --- /dev/null +++ b/source/server/connection_manager.hpp @@ -0,0 +1,48 @@ +// +// connection_manager.hpp +// ~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_CONNECTION_MANAGER_HPP +#define HTTP_CONNECTION_MANAGER_HPP + +#include +#include "connection.hpp" + +namespace http { +namespace server { + +/// Manages open connections so that they may be cleanly stopped when the server +/// needs to shut down. +class connection_manager +{ +public: + connection_manager(const connection_manager&) = delete; + connection_manager& operator=(const connection_manager&) = delete; + + /// Construct a connection manager. + connection_manager(); + + /// Add the specified connection to the manager and start it. + void start(connection_ptr c); + + /// Stop the specified connection. + void stop(connection_ptr c); + + /// Stop all connections. + void stop_all(); + +private: + /// The managed connections. + std::set connections_; +}; + +} // namespace server +} // namespace http + +#endif // HTTP_CONNECTION_MANAGER_HPP diff --git a/source/server/header.hpp b/source/server/header.hpp new file mode 100755 index 0000000..524097e --- /dev/null +++ b/source/server/header.hpp @@ -0,0 +1,28 @@ +// +// header.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_HEADER_HPP +#define HTTP_HEADER_HPP + +#include + +namespace http { +namespace server { + +struct header +{ + std::string name; + std::string value; +}; + +} // namespace server +} // namespace http + +#endif // HTTP_HEADER_HPP diff --git a/source/server/main.cpp b/source/server/main.cpp new file mode 100755 index 0000000..e57e7e6 --- /dev/null +++ b/source/server/main.cpp @@ -0,0 +1,28 @@ +// +// main.cpp +// ~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + const std::string ip = "0.0.0.0"; + const std::string port = "8000"; + const std::string path = "./"; + + std::cout << "Serving HTTP on " << ip << " port " << port << " ..." << std::endl; + + http::server::server s(ip, port, path); + s.run(); + + return 0; +} diff --git a/source/server/mime_types.cpp b/source/server/mime_types.cpp new file mode 100755 index 0000000..5741410 --- /dev/null +++ b/source/server/mime_types.cpp @@ -0,0 +1,46 @@ +// +// mime_types.cpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "mime_types.hpp" + +namespace http { +namespace server { +namespace mime_types { + +struct mapping +{ + const char* extension; + const char* mime_type; +} mappings[] = +{ + { "gif", "image/gif" }, + { "htm", "text/html" }, + { "html", "text/html" }, + { "jpg", "image/jpeg" }, + { "png", "image/png" }, + { "css", "text/css" } +}; + +std::string extension_to_type(const std::string& extension) +{ + for (mapping m: mappings) + { + if (m.extension == extension) + { + return m.mime_type; + } + } + + return "text/plain"; +} + +} // namespace mime_types +} // namespace server +} // namespace http diff --git a/source/server/mime_types.hpp b/source/server/mime_types.hpp new file mode 100755 index 0000000..e5881f2 --- /dev/null +++ b/source/server/mime_types.hpp @@ -0,0 +1,27 @@ +// +// mime_types.hpp +// ~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_MIME_TYPES_HPP +#define HTTP_MIME_TYPES_HPP + +#include + +namespace http { +namespace server { +namespace mime_types { + +/// Convert a file extension into a MIME type. +std::string extension_to_type(const std::string& extension); + +} // namespace mime_types +} // namespace server +} // namespace http + +#endif // HTTP_MIME_TYPES_HPP diff --git a/source/server/reply.cpp b/source/server/reply.cpp new file mode 100755 index 0000000..5651d65 --- /dev/null +++ b/source/server/reply.cpp @@ -0,0 +1,255 @@ +// +// reply.cpp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "reply.hpp" +#include + +namespace http { +namespace server { + +namespace status_strings { + +const std::string ok = + "HTTP/1.0 200 OK\r\n"; +const std::string created = + "HTTP/1.0 201 Created\r\n"; +const std::string accepted = + "HTTP/1.0 202 Accepted\r\n"; +const std::string no_content = + "HTTP/1.0 204 No Content\r\n"; +const std::string multiple_choices = + "HTTP/1.0 300 Multiple Choices\r\n"; +const std::string moved_permanently = + "HTTP/1.0 301 Moved Permanently\r\n"; +const std::string moved_temporarily = + "HTTP/1.0 302 Moved Temporarily\r\n"; +const std::string not_modified = + "HTTP/1.0 304 Not Modified\r\n"; +const std::string bad_request = + "HTTP/1.0 400 Bad Request\r\n"; +const std::string unauthorized = + "HTTP/1.0 401 Unauthorized\r\n"; +const std::string forbidden = + "HTTP/1.0 403 Forbidden\r\n"; +const std::string not_found = + "HTTP/1.0 404 Not Found\r\n"; +const std::string internal_server_error = + "HTTP/1.0 500 Internal Server Error\r\n"; +const std::string not_implemented = + "HTTP/1.0 501 Not Implemented\r\n"; +const std::string bad_gateway = + "HTTP/1.0 502 Bad Gateway\r\n"; +const std::string service_unavailable = + "HTTP/1.0 503 Service Unavailable\r\n"; + +boost::asio::const_buffer to_buffer(reply::status_type status) +{ + switch (status) + { + case reply::ok: + return boost::asio::buffer(ok); + case reply::created: + return boost::asio::buffer(created); + case reply::accepted: + return boost::asio::buffer(accepted); + case reply::no_content: + return boost::asio::buffer(no_content); + case reply::multiple_choices: + return boost::asio::buffer(multiple_choices); + case reply::moved_permanently: + return boost::asio::buffer(moved_permanently); + case reply::moved_temporarily: + return boost::asio::buffer(moved_temporarily); + case reply::not_modified: + return boost::asio::buffer(not_modified); + case reply::bad_request: + return boost::asio::buffer(bad_request); + case reply::unauthorized: + return boost::asio::buffer(unauthorized); + case reply::forbidden: + return boost::asio::buffer(forbidden); + case reply::not_found: + return boost::asio::buffer(not_found); + case reply::internal_server_error: + return boost::asio::buffer(internal_server_error); + case reply::not_implemented: + return boost::asio::buffer(not_implemented); + case reply::bad_gateway: + return boost::asio::buffer(bad_gateway); + case reply::service_unavailable: + return boost::asio::buffer(service_unavailable); + default: + return boost::asio::buffer(internal_server_error); + } +} + +} // namespace status_strings + +namespace misc_strings { + +const char name_value_separator[] = { ':', ' ' }; +const char crlf[] = { '\r', '\n' }; + +} // namespace misc_strings + +std::vector reply::to_buffers() +{ + std::vector buffers; + buffers.push_back(status_strings::to_buffer(status)); + for (std::size_t i = 0; i < headers.size(); ++i) + { + header& h = headers[i]; + buffers.push_back(boost::asio::buffer(h.name)); + buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); + buffers.push_back(boost::asio::buffer(h.value)); + buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + } + buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + buffers.push_back(boost::asio::buffer(content)); + return buffers; +} + +namespace stock_replies { + +const char ok[] = ""; +const char created[] = + "" + "Created" + "

201 Created

" + ""; +const char accepted[] = + "" + "Accepted" + "

202 Accepted

" + ""; +const char no_content[] = + "" + "No Content" + "

204 Content

" + ""; +const char multiple_choices[] = + "" + "Multiple Choices" + "

300 Multiple Choices

" + ""; +const char moved_permanently[] = + "" + "Moved Permanently" + "

301 Moved Permanently

" + ""; +const char moved_temporarily[] = + "" + "Moved Temporarily" + "

302 Moved Temporarily

" + ""; +const char not_modified[] = + "" + "Not Modified" + "

304 Not Modified

" + ""; +const char bad_request[] = + "" + "Bad Request" + "

400 Bad Request

" + ""; +const char unauthorized[] = + "" + "Unauthorized" + "

401 Unauthorized

" + ""; +const char forbidden[] = + "" + "Forbidden" + "

403 Forbidden

" + ""; +const char not_found[] = + "" + "Not Found" + "

404 Not Found

" + ""; +const char internal_server_error[] = + "" + "Internal Server Error" + "

500 Internal Server Error

" + ""; +const char not_implemented[] = + "" + "Not Implemented" + "

501 Not Implemented

" + ""; +const char bad_gateway[] = + "" + "Bad Gateway" + "

502 Bad Gateway

" + ""; +const char service_unavailable[] = + "" + "Service Unavailable" + "

503 Service Unavailable

" + ""; + +std::string to_string(reply::status_type status) +{ + switch (status) + { + case reply::ok: + return ok; + case reply::created: + return created; + case reply::accepted: + return accepted; + case reply::no_content: + return no_content; + case reply::multiple_choices: + return multiple_choices; + case reply::moved_permanently: + return moved_permanently; + case reply::moved_temporarily: + return moved_temporarily; + case reply::not_modified: + return not_modified; + case reply::bad_request: + return bad_request; + case reply::unauthorized: + return unauthorized; + case reply::forbidden: + return forbidden; + case reply::not_found: + return not_found; + case reply::internal_server_error: + return internal_server_error; + case reply::not_implemented: + return not_implemented; + case reply::bad_gateway: + return bad_gateway; + case reply::service_unavailable: + return service_unavailable; + default: + return internal_server_error; + } +} + +} // namespace stock_replies + +reply reply::stock_reply(reply::status_type status) +{ + reply rep; + rep.status = status; + rep.content = stock_replies::to_string(status); + rep.headers.resize(2); + rep.headers[0].name = "Content-Length"; + rep.headers[0].value = std::to_string(rep.content.size()); + rep.headers[1].name = "Content-Type"; + rep.headers[1].value = "text/html"; + return rep; +} + +} // namespace server +} // namespace http diff --git a/source/server/reply.hpp b/source/server/reply.hpp new file mode 100755 index 0000000..f391121 --- /dev/null +++ b/source/server/reply.hpp @@ -0,0 +1,64 @@ +// +// reply.hpp +// ~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_REPLY_HPP +#define HTTP_REPLY_HPP + +#include +#include +#include +#include "header.hpp" + +namespace http { +namespace server { + +/// A reply to be sent to a client. +struct reply +{ + /// The status of the reply. + enum status_type + { + ok = 200, + created = 201, + accepted = 202, + no_content = 204, + multiple_choices = 300, + moved_permanently = 301, + moved_temporarily = 302, + not_modified = 304, + bad_request = 400, + unauthorized = 401, + forbidden = 403, + not_found = 404, + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503 + } status; + + /// The headers to be included in the reply. + std::vector
headers; + + /// The content to be sent in the reply. + std::string content; + + /// Convert the reply into a vector of buffers. The buffers do not own the + /// underlying memory blocks, therefore the reply object must remain valid and + /// not be changed until the write operation has completed. + std::vector to_buffers(); + + /// Get a stock reply. + static reply stock_reply(status_type status); +}; + +} // namespace server +} // namespace http + +#endif // HTTP_REPLY_HPP diff --git a/source/server/request.hpp b/source/server/request.hpp new file mode 100755 index 0000000..da858c3 --- /dev/null +++ b/source/server/request.hpp @@ -0,0 +1,35 @@ +// +// request.hpp +// ~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_REQUEST_HPP +#define HTTP_REQUEST_HPP + +#include +#include +#include "header.hpp" + +namespace http { +namespace server { + +/// A request received from a client. +struct request +{ + std::string method; + std::string uri; + int http_version_major; + int http_version_minor; + std::vector
headers; + std::string body; +}; + +} // namespace server +} // namespace http + +#endif // HTTP_REQUEST_HPP diff --git a/source/server/request_handler.cpp b/source/server/request_handler.cpp new file mode 100755 index 0000000..26ed26b --- /dev/null +++ b/source/server/request_handler.cpp @@ -0,0 +1,173 @@ +// +// request_handler.cpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "request_handler.hpp" +#include +#include +#include +#include +#include +#include "mime_types.hpp" +#include "reply.hpp" +#include "request.hpp" + +namespace http { +namespace server { + +request_handler::request_handler(const std::string& doc_root) + : doc_root_(doc_root), + simulation_(2000, 50000000, 100000) +{ + simulation_.add_entity(brain_); +} + +void request_handler::handle_request(const request& req, reply& rep) +{ + // Decode url to path. + std::string request_path; + if (!url_decode(req.uri, request_path)) + { + rep = reply::stock_reply(reply::bad_request); + return; + } + + // Request path must be absolute and not contain "..". + if (request_path.empty() || request_path[0] != '/' + || request_path.find("..") != std::string::npos) + { + rep = reply::stock_reply(reply::bad_request); + return; + } + + if (request_path.substr(0, 5) == "/api/") + { + auto target = request_path.substr(5); + std::string response_string; + + if (target == "load_script") + { + brain_.load(req.body); + } + else if (target == "state") + { + simulation_.step();// simulation_.step(); simulation_.step(); + response_string = brain_.serialize_state(); + } + else if (target == "structure") + { + response_string = brain_.serialize_structure(); + } + else if (target == "step") + { + simulation_.step(); + } + else if (target == "get_sim_time") + { + response_string = std::to_string(simulation_.get_total_sim_time()); + } + else if (target == "train") + { + auto json = nlohmann::json::parse(req.body); + brain_.train(json["image"], json["expected"]); + } + else if (target == "input_image") + { + brain_.set_input_image(req.body); + } + + // new std::thread([](cort::simulation *s) { s->start(); }, &simulation_); + + rep.status = reply::ok; + rep.content = response_string; + rep.headers.resize(2); + rep.headers[0].name = "Content-Length"; + rep.headers[0].value = std::to_string(rep.content.size()); + rep.headers[1].name = "Content-Type"; + rep.headers[1].value = mime_types::extension_to_type("text/json"); + return; + } + + // If path ends in slash (i.e. is a directory) then add "index.html". + if (request_path[request_path.size() - 1] == '/') + { + request_path += "index.html"; + } + + // Determine the file extension. + std::size_t last_slash_pos = request_path.find_last_of("/"); + std::size_t last_dot_pos = request_path.find_last_of("."); + std::string extension; + if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos) + { + extension = request_path.substr(last_dot_pos + 1); + } + + // Open the file to send back. + std::string full_path = doc_root_ + request_path; + std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary); + if (!is) + { + rep = reply::stock_reply(reply::not_found); + return; + } + + // Fill out the reply to be sent to the client. + rep.status = reply::ok; + char buf[512]; + while (is.read(buf, sizeof(buf)).gcount() > 0) + rep.content.append(buf, is.gcount()); + rep.headers.resize(2); + rep.headers[0].name = "Content-Length"; + rep.headers[0].value = std::to_string(rep.content.size()); + rep.headers[1].name = "Content-Type"; + rep.headers[1].value = mime_types::extension_to_type(extension); +} + +bool request_handler::url_decode(const std::string& in, std::string& out) +{ + out.clear(); + out.reserve(in.size()); + for (std::size_t i = 0; i < in.size(); ++i) + { + if (in[i] == '%') + { + if (i + 3 <= in.size()) + { + int value = 0; + std::istringstream is(in.substr(i + 1, 2)); + if (is >> std::hex >> value) + { + out += static_cast(value); + i += 2; + } + else + { + return false; + } + } + else + { + return false; + } + } + else if (in[i] == '+') + { + out += ' '; + } + else + { + out += in[i]; + } + } + return true; +} + +} // namespace server +} // namespace http diff --git a/source/server/request_handler.hpp b/source/server/request_handler.hpp new file mode 100755 index 0000000..d8c83a4 --- /dev/null +++ b/source/server/request_handler.hpp @@ -0,0 +1,52 @@ +// +// request_handler.hpp +// ~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_REQUEST_HANDLER_HPP +#define HTTP_REQUEST_HANDLER_HPP + +#include + +#include "simulation/simulation.hpp" + +namespace http { +namespace server { + +struct reply; +struct request; + +/// The common handler for all incoming requests. +class request_handler +{ +public: + request_handler(const request_handler&) = delete; + request_handler& operator=(const request_handler&) = delete; + + /// Construct with a directory containing files to be served. + explicit request_handler(const std::string& doc_root); + + /// Handle a request and produce a reply. + void handle_request(const request& req, reply& rep); + +private: + /// The directory containing the files to be served. + std::string doc_root_; + + /// Perform URL-decoding on a string. Returns false if the encoding was + /// invalid. + static bool url_decode(const std::string& in, std::string& out); + + cort::simulation simulation_; + cort::brain brain_; +}; + +} // namespace server +} // namespace http + +#endif // HTTP_REQUEST_HANDLER_HPP diff --git a/source/server/request_parser.cpp b/source/server/request_parser.cpp new file mode 100755 index 0000000..457d4f2 --- /dev/null +++ b/source/server/request_parser.cpp @@ -0,0 +1,333 @@ +// +// request_parser.cpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include +#include "request_parser.hpp" +#include "request.hpp" + +namespace http { +namespace server { + +request_parser::request_parser() + : state_(method_start), + body_content_length_(0) +{ +} + +void request_parser::reset() +{ + state_ = method_start; + body_content_length_ = 0; +} + +request_parser::result_type request_parser::consume(request& req, char input) +{ + switch (state_) + { + case method_start: + if (!is_char(input) || is_ctl(input) || is_tspecial(input)) + { + return bad; + } + else + { + state_ = method; + req.method.push_back(input); + return indeterminate; + } + case method: + if (input == ' ') + { + state_ = uri; + return indeterminate; + } + else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) + { + return bad; + } + else + { + req.method.push_back(input); + return indeterminate; + } + case uri: + if (input == ' ') + { + state_ = http_version_h; + return indeterminate; + } + else if (is_ctl(input)) + { + return bad; + } + else + { + req.uri.push_back(input); + return indeterminate; + } + case http_version_h: + if (input == 'H') + { + state_ = http_version_t_1; + return indeterminate; + } + else + { + return bad; + } + case http_version_t_1: + if (input == 'T') + { + state_ = http_version_t_2; + return indeterminate; + } + else + { + return bad; + } + case http_version_t_2: + if (input == 'T') + { + state_ = http_version_p; + return indeterminate; + } + else + { + return bad; + } + case http_version_p: + if (input == 'P') + { + state_ = http_version_slash; + return indeterminate; + } + else + { + return bad; + } + case http_version_slash: + if (input == '/') + { + req.http_version_major = 0; + req.http_version_minor = 0; + state_ = http_version_major_start; + return indeterminate; + } + else + { + return bad; + } + case http_version_major_start: + if (is_digit(input)) + { + req.http_version_major = req.http_version_major * 10 + input - '0'; + state_ = http_version_major; + return indeterminate; + } + else + { + return bad; + } + case http_version_major: + if (input == '.') + { + state_ = http_version_minor_start; + return indeterminate; + } + else if (is_digit(input)) + { + req.http_version_major = req.http_version_major * 10 + input - '0'; + return indeterminate; + } + else + { + return bad; + } + case http_version_minor_start: + if (is_digit(input)) + { + req.http_version_minor = req.http_version_minor * 10 + input - '0'; + state_ = http_version_minor; + return indeterminate; + } + else + { + return bad; + } + case http_version_minor: + if (input == '\r') + { + state_ = expecting_newline_1; + return indeterminate; + } + else if (is_digit(input)) + { + req.http_version_minor = req.http_version_minor * 10 + input - '0'; + return indeterminate; + } + else + { + return bad; + } + case expecting_newline_1: + if (input == '\n') + { + state_ = header_line_start; + return indeterminate; + } + else + { + return bad; + } + case header_line_start: + if (input == '\r') + { + state_ = expecting_newline_3; + return indeterminate; + } + else if (!req.headers.empty() && (input == ' ' || input == '\t')) + { + state_ = header_lws; + return indeterminate; + } + else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) + { + return bad; + } + else + { + req.headers.push_back(header()); + req.headers.back().name.push_back(input); + state_ = header_name; + return indeterminate; + } + case header_lws: + if (input == '\r') + { + state_ = expecting_newline_2; + return indeterminate; + } + else if (input == ' ' || input == '\t') + { + return indeterminate; + } + else if (is_ctl(input)) + { + return bad; + } + else + { + state_ = header_value; + req.headers.back().value.push_back(input); + return indeterminate; + } + case header_name: + if (input == ':') + { + state_ = space_before_header_value; + return indeterminate; + } + else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) + { + return bad; + } + else + { + req.headers.back().name.push_back(input); + return indeterminate; + } + case space_before_header_value: + if (input == ' ') + { + state_ = header_value; + return indeterminate; + } + else + { + return bad; + } + case header_value: + if (input == '\r') + { + if (req.headers.back().name == "Content-Length") + { + body_content_length_ = std::stoi(req.headers.back().value); + } + state_ = expecting_newline_2; + return indeterminate; + } + else if (is_ctl(input)) + { + return bad; + } + else + { + req.headers.back().value.push_back(input); + return indeterminate; + } + case expecting_newline_2: + if (input == '\n') + { + state_ = header_line_start; + return indeterminate; + } + else + { + return bad; + } + case expecting_newline_3: + if (body_content_length_ > 0) + { + state_ = body; + return indeterminate; + } + return (input == '\n') ? good : bad; + case body: + req.body.push_back(input); + if (req.body.length() < body_content_length_) + { + return indeterminate; + } + return good; + default: + return bad; + } +} + +bool request_parser::is_char(int c) +{ + return c >= 0 && c <= 127; +} + +bool request_parser::is_ctl(int c) +{ + return (c >= 0 && c <= 31) || (c == 127); +} + +bool request_parser::is_tspecial(int c) +{ + switch (c) + { + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '/': case '[': case ']': case '?': case '=': + case '{': case '}': case ' ': case '\t': + return true; + default: + return false; + } +} + +bool request_parser::is_digit(int c) +{ + return c >= '0' && c <= '9'; +} + +} // namespace server +} // namespace http diff --git a/source/server/request_parser.hpp b/source/server/request_parser.hpp new file mode 100755 index 0000000..c636a6b --- /dev/null +++ b/source/server/request_parser.hpp @@ -0,0 +1,99 @@ +// +// request_parser.hpp +// ~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_REQUEST_PARSER_HPP +#define HTTP_REQUEST_PARSER_HPP + +#include + +namespace http { +namespace server { + +struct request; + +/// Parser for incoming requests. +class request_parser +{ +public: + /// Construct ready to parse the request method. + request_parser(); + + /// Reset to initial parser state. + void reset(); + + /// Result of parse. + enum result_type { good, bad, indeterminate }; + + /// Parse some data. The enum return value is good when a complete request has + /// been parsed, bad if the data is invalid, indeterminate when more data is + /// required. The InputIterator return value indicates how much of the input + /// has been consumed. + template + std::tuple parse(request& req, + InputIterator begin, InputIterator end) + { + while (begin != end) + { + result_type result = consume(req, *begin++); + if (result == good || result == bad) + return std::make_tuple(result, begin); + } + return std::make_tuple(indeterminate, begin); + } + +private: + /// Handle the next character of input. + result_type consume(request& req, char input); + + /// Check if a byte is an HTTP character. + static bool is_char(int c); + + /// Check if a byte is an HTTP control character. + static bool is_ctl(int c); + + /// Check if a byte is defined as an HTTP tspecial character. + static bool is_tspecial(int c); + + /// Check if a byte is a digit. + static bool is_digit(int c); + + /// The current state of the parser. + enum state + { + method_start, + method, + uri, + http_version_h, + http_version_t_1, + http_version_t_2, + http_version_p, + http_version_slash, + http_version_major_start, + http_version_major, + http_version_minor_start, + http_version_minor, + expecting_newline_1, + header_line_start, + header_lws, + header_name, + space_before_header_value, + header_value, + expecting_newline_2, + expecting_newline_3, + body + } state_; + + std::size_t body_content_length_; +}; + +} // namespace server +} // namespace http + +#endif // HTTP_REQUEST_PARSER_HPP diff --git a/source/server/server.cpp b/source/server/server.cpp new file mode 100755 index 0000000..25ad6b4 --- /dev/null +++ b/source/server/server.cpp @@ -0,0 +1,94 @@ +// +// server.cpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "server.hpp" +#include +#include + +namespace http { +namespace server { + +server::server(const std::string& address, const std::string& port, + const std::string& doc_root) + : io_service_(), + signals_(io_service_), + acceptor_(io_service_), + connection_manager_(), + socket_(io_service_), + request_handler_(doc_root) +{ + // Register to handle the signals that indicate when the server should exit. + // It is safe to register for the same signal multiple times in a program, + // provided all registration for the specified signal is made through Asio. + signals_.add(SIGINT); + signals_.add(SIGTERM); +#if defined(SIGQUIT) + signals_.add(SIGQUIT); +#endif // defined(SIGQUIT) + + do_await_stop(); + + // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). + boost::asio::ip::tcp::resolver resolver(io_service_); + boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve({address, port}); + acceptor_.open(endpoint.protocol()); + acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + acceptor_.bind(endpoint); + acceptor_.listen(); + + do_accept(); +} + +void server::run() +{ + // The io_service::run() call will block until all asynchronous operations + // have finished. While the server is running, there is always at least one + // asynchronous operation outstanding: the asynchronous accept call waiting + // for new incoming connections. + io_service_.run(); +} + +void server::do_accept() +{ + acceptor_.async_accept(socket_, + [this](boost::system::error_code ec) + { + // Check whether the server was stopped by a signal before this + // completion handler had a chance to run. + if (!acceptor_.is_open()) + { + return; + } + + if (!ec) + { + connection_manager_.start(std::make_shared( + std::move(socket_), connection_manager_, request_handler_)); + } + + do_accept(); + }); +} + +void server::do_await_stop() +{ + signals_.async_wait( + [this](boost::system::error_code /*ec*/, int /*signo*/) + { + // The server is stopped by cancelling all outstanding asynchronous + // operations. Once all operations have finished the io_service::run() + // call will exit. + acceptor_.close(); + connection_manager_.stop_all(); + }); +} + +} // namespace server +} // namespace http diff --git a/source/server/server.hpp b/source/server/server.hpp new file mode 100755 index 0000000..39fc3a4 --- /dev/null +++ b/source/server/server.hpp @@ -0,0 +1,67 @@ +// +// server.hpp +// ~~~~~~~~~~ +// +// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef HTTP_SERVER_HPP +#define HTTP_SERVER_HPP + +#include +#include +#include "connection.hpp" +#include "connection_manager.hpp" +#include "request_handler.hpp" + +namespace http { +namespace server { + +/// The top-level class of the HTTP server. +class server +{ +public: + server(const server&) = delete; + server& operator=(const server&) = delete; + + /// Construct the server to listen on the specified TCP address and port, and + /// serve up files from the given directory. + explicit server(const std::string& address, const std::string& port, + const std::string& doc_root); + + /// Run the server's io_service loop. + void run(); + +private: + /// Perform an asynchronous accept operation. + void do_accept(); + + /// Wait for a request to stop the server. + void do_await_stop(); + + /// The io_service used to perform asynchronous operations. + boost::asio::io_service io_service_; + + /// The signal_set is used to register for process termination notifications. + boost::asio::signal_set signals_; + + /// Acceptor used to listen for incoming connections. + boost::asio::ip::tcp::acceptor acceptor_; + + /// The connection manager which owns all live connections. + connection_manager connection_manager_; + + /// The next socket to be accepted. + boost::asio::ip::tcp::socket socket_; + + /// The handler for all incoming requests. + request_handler request_handler_; +}; + +} // namespace server +} // namespace http + +#endif // HTTP_SERVER_HPP diff --git a/source/simulation/entity.cpp b/source/simulation/entity.cpp new file mode 100644 index 0000000..ede72db --- /dev/null +++ b/source/simulation/entity.cpp @@ -0,0 +1,39 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "simulation/entity.hpp" + +namespace cort { + +int entity::next_id_ = 0; + +entity::entity() : + id_(next_id_++) +{ +} + +entity::~entity() +{ +} + +} // namespace cort diff --git a/source/simulation/entity.hpp b/source/simulation/entity.hpp new file mode 100644 index 0000000..2ea69cf --- /dev/null +++ b/source/simulation/entity.hpp @@ -0,0 +1,51 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +namespace cort { + +class layer; + +class entity +{ +public: + entity(); + virtual ~entity() = 0; + + int get_id() { return id_; } + + virtual std::string serialize_structure() const = 0; + virtual std::string serialize_state() const = 0; + + virtual void update(double time) = 0; + virtual void reset() = 0; + virtual layer *get_layer(const std::string &name) = 0; + +private: + static int next_id_; + int id_; +}; + +} // namespace cort diff --git a/source/simulation/random_number_generator.cpp b/source/simulation/random_number_generator.cpp new file mode 100644 index 0000000..3d1af5f --- /dev/null +++ b/source/simulation/random_number_generator.cpp @@ -0,0 +1,133 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include + +#include "simulation/random_number_generator.hpp" + +namespace cort { + +random_number_generator::random_number_generator() +{ + std::time_t time = std::time(0); + seed(static_cast(time)); +} + +random_number_generator::random_number_generator(int seed) +{ + this->seed(seed); +} + +random_number_generator::~random_number_generator() +{ + +} + +unsigned int random_number_generator::rand_uint() +{ + unsigned long long int sum; + sum = (unsigned long long int)2111111111UL * (unsigned long long int)x[3] + + (unsigned long long int)1492 * (unsigned long long int)(x[2]) + + (unsigned long long int)1776 * (unsigned long long int)(x[1]) + + (unsigned long long int)5115 * (unsigned long long int)(x[0]) + + (unsigned long long int)x[4]; + x[3] = x[2]; + x[2] = x[1]; + x[1] = x[0]; + x[4] = (unsigned int)(sum >> 32); + x[0] = (unsigned int)sum; + return x[0]; +} + +double random_number_generator::rand_double() +{ + return (double)rand_uint() * (1. / (65536. * 65536.)); +} + +double random_number_generator::rand_double(double max) +{ + return rand_double() * max; +} + +double random_number_generator::rand_double(double min, double max) +{ + return min + rand_double() * (max - min); +} + +int random_number_generator::rand_int(int max) +{ + return rand_int(0, max); +} + +int random_number_generator::rand_int(int min, int max) +{ + if(max <= min) + { + if(max == min) + { + return min; + } + else + { + return 0x80000000; + } + } + + unsigned int interval = (unsigned int)(max - min); + return ((int)(interval * rand_double())) + min; +} + +void random_number_generator::seed(int seed) +{ + int i; + unsigned int s = seed; + + for(i = 0; i < 5; i++) + { + s = s * 29943829 - 1; + x[i] = s; + } + + for(i = 0; i < 19; i++) + { + rand_uint(); + } +} + +std::istream &operator>>(std::istream &in, random_number_generator &rng) +{ + for(int i = 0; i < 5; i++) + { + in >> rng.x[i]; + } + + return in; +} + +std::ostream &operator<<(std::ostream &out, const random_number_generator &rng) +{ + out << rng.x[0] << " " << rng.x[1] << " " << rng.x[2] << " " << rng.x[3] << " " << rng.x[4]; + return out; +} + +} // namespace cort diff --git a/source/simulation/random_number_generator.hpp b/source/simulation/random_number_generator.hpp new file mode 100644 index 0000000..2f49800 --- /dev/null +++ b/source/simulation/random_number_generator.hpp @@ -0,0 +1,51 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +namespace cort { + +class random_number_generator +{ +public: + random_number_generator(int seed); + random_number_generator(); + ~random_number_generator(); + + void seed(int seed); + int rand_int(int min, int max); + int rand_int(int max); + double rand_double(); + double rand_double(double min); + double rand_double(double min, double max); + unsigned int rand_uint(); + + friend std::ostream &operator<<(std::ostream &out, const random_number_generator &rng); + friend std::istream &operator>>(std::istream &out, random_number_generator &rng); + +private: + unsigned int x[5]; +}; + +} // namespace cort diff --git a/source/simulation/simulation.cpp b/source/simulation/simulation.cpp new file mode 100644 index 0000000..be47b4d --- /dev/null +++ b/source/simulation/simulation.cpp @@ -0,0 +1,109 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include +#include + +#include "simulation/simulation.hpp" +#include "brain/connection.hpp" +#include "simulation/entity.hpp" +#include "simulation/timer.hpp" + +namespace cort { + +simulation::simulation(double timeToSimulate, double stepsPerRealSec, double stepsPerSimSec) : + current_step_(0), + end_step_(static_cast(timeToSimulate * stepsPerSimSec)), + real_seconds_per_step_(1 / stepsPerRealSec), + steps_per_real_second_(stepsPerRealSec), + sim_seconds_per_step_(1 / stepsPerSimSec), + steps_per_sim_second_(stepsPerSimSec), + sim_time_(timeToSimulate) +{ + +} + +simulation::~simulation() +{ + +} + +void simulation::start() +{ + std::cout << "simulation started" << std::endl; + + timer_.reset(); + double currentTime = timer_.get_time_in_seconds(); + double startTime = currentTime; + + step(); + + while(current_step_ < end_step_) + { + while(timer_.get_time_in_seconds() - currentTime < real_seconds_per_step_) + { + } + + currentTime = timer_.get_time_in_seconds(); + std::cout << current_step_ << std::endl; + + step(); + } + + std::printf("Final simulation rate: %fs(sim)/s(real)\n", sim_time_ / (currentTime - startTime)); +} + +void simulation::stop() +{ + +} + +void simulation::step() +{ + for(auto entity : entities_) + { + entity->update(current_step_ * sim_seconds_per_step_); + } + + current_step_++; +} + +void simulation::reset() +{ + for(auto entity : entities_) + { + entity->reset(); + } +} + +std::string simulation::serialize_entity_structure(std::size_t entity_index) +{ + return entities_[entity_index]->serialize_structure(); +} + +std::string simulation::serialize_entity_state(std::size_t entity_index) +{ + return entities_[entity_index]->serialize_state(); +} + +} // namespace cort diff --git a/source/simulation/simulation.hpp b/source/simulation/simulation.hpp new file mode 100644 index 0000000..9577118 --- /dev/null +++ b/source/simulation/simulation.hpp @@ -0,0 +1,95 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +#include "brain/brain.hpp" +#include "simulation/trainer.hpp" +#include "simulation/timer.hpp" + +namespace cort { + +class entity; + +class simulation +{ + public: + simulation(double timeToSimulate, double stepsPerRealSec, double stepsPerSimSec); + ~simulation(); + + void start(); + void stop(); + void reset(); + void step(); + + std::string serialize_entity_structure(std::size_t entity_index); + std::string serialize_entity_state(std::size_t entity_index); + + void add_entity(entity &ent) { entities_.push_back(&ent); } + + double get_percent_completion() { return ((double)current_step_) / end_step_; } + unsigned int get_current_step() { return current_step_; } + double get_total_sim_time() { return current_step_ * sim_seconds_per_step_; } + + void set_real_seconds_per_step(double seconds) + { + real_seconds_per_step_ = seconds; + steps_per_real_second_ = 1 / seconds; + } + double get_real_seconds_per_step() { return real_seconds_per_step_; } + + void set_steps_per_real_second(double steps) + { + steps_per_real_second_ = steps; + real_seconds_per_step_ = 1 / steps; + } + double get_steps_per_real_second() { return steps_per_real_second_; } + + void set_sim_seconds_per_step(double seconds) + { + sim_seconds_per_step_ = seconds; + steps_per_sim_second_ = 1 / seconds; + } + double get_sim_seconds_per_step() { return sim_seconds_per_step_; } + + void set_steps_per_sim_second(double steps) + { + steps_per_sim_second_ = steps; + sim_seconds_per_step_ = 1 / steps; + } + double get_steps_per_sim_second() { return steps_per_sim_second_; } + + private: + std::vector entities_; + int current_step_; + int end_step_; + double real_seconds_per_step_; + double steps_per_real_second_; + double sim_seconds_per_step_; + double steps_per_sim_second_; + double sim_time_; + timer timer_; +}; + +} // namespace cort diff --git a/source/simulation/timer.cpp b/source/simulation/timer.cpp new file mode 100644 index 0000000..b0ec066 --- /dev/null +++ b/source/simulation/timer.cpp @@ -0,0 +1,88 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "simulation/timer.hpp" + +namespace cort { + +timer::timer() +{ + reset(); +} + +timer::~timer() +{ +} + +void timer::reset() +{ +#ifndef _WIN32 + zeroClock = clock(); + gettimeofday(&start, NULL); +#else + LARGE_INTEGER ticksPerSecond; + QueryPerformanceFrequency(&ticksPerSecond); + updateFrequency = double(ticksPerSecond.QuadPart) / 1000000.0; +#endif +} + +unsigned long timer::get_time() +{ +#ifndef _WIN32 + struct timeval now; + gettimeofday(&now, NULL); + return (now.tv_sec - start.tv_sec) * 1000000 + (now.tv_usec - start.tv_usec); +#else + LARGE_INTEGER tick; + QueryPerformanceCounter(&tick); + return static_cast((tick.QuadPart - start) / updateFrequency); +#endif +} + +double timer::get_time_in_seconds() +{ + return get_time() / 1000000.0; +} + +double timer::get_resolution() +{ + reset(); + const double t0 = get_time() / 1000000.0; + double t1, t2; + + do + { + t1 = get_time() / 1000000.0; + } + while(t1 == t0); + + do + { + t2 = get_time() / 1000000.0; + } + while(t2 == t1); + + return (t2 - t1); +} + +} // namespace cort diff --git a/source/simulation/timer.hpp b/source/simulation/timer.hpp new file mode 100644 index 0000000..7f296b5 --- /dev/null +++ b/source/simulation/timer.hpp @@ -0,0 +1,56 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include +#include +#ifndef _WIN32 +#include +#else +#include +#endif + +namespace cort { + +class timer +{ + public: + timer(); + ~timer(); + + void reset(); + unsigned long get_time(); + double get_time_in_seconds(); + double get_resolution(); + + private: +#ifndef _WIN32 + struct timeval start; + clock_t zeroClock; +#else + int64_t start; + double updateFrequency; +#endif +}; + +} // namespace cort diff --git a/source/simulation/trainer.cpp b/source/simulation/trainer.cpp new file mode 100644 index 0000000..4a66e4f --- /dev/null +++ b/source/simulation/trainer.cpp @@ -0,0 +1,331 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#include "simulation/trainer.hpp" +#include "brain/brain.hpp" +#include "interface/script.hpp" +#include "simulation/entity.hpp" + +namespace cort { + +trainer::trainer(const std::string &filename, brain * /*ent*/) : script_(filename) {};/* : + m_CurrentTime(0), + m_DeltaTime(0), + m_WaitStart(0.), + m_WaitDuration(0.), + m_Waiting(false), + m_CurrentRepeats(0), + m_NumRepeats(0), + m_TimeBtStartMsgAndInput(0.), + m_TimeToWaitForTestResponse(0.), + m_TimeBtOutputAndEndMsg(0.), + m_TimeBtTrainInAndOut(0.), + m_TimeBtTrainIterations(0.), + m_TimeBtTestIterations(0.), + m_TotalTrainingIterations(0), + m_TotalTestingIterations(0), + m_CurrTrainingIteration(0), + m_CurrTestingIteration(0), + m_ID(0), + m_Input(""), + m_ExpectedOutput(""), + m_BciReward(nullptr), + m_BciInput(nullptr), + m_BciOutput(nullptr), + m_BciOutputTrainer(nullptr), + m_Script(filename), + m_State(TeachStartMsg) +{ + std::string inputCommandsFile = m_Script.GetNextString(); + std::string outputCommandsFile = m_Script.GetNextString(); + + m_BciInput = new BCI_SpeechFromFile(ent->get_layer("input"), inputCommandsFile); + m_BciReward = new BCI_RewardPunishment(ent->get_layer("output")); + m_BciOutput = new BCI_SpeechFromFile(ent->get_layer("output"), outputCommandsFile); + m_BciOutputTrainer = new BCI_SpeechFromFile(ent->get_layer("outputTrainer"), outputCommandsFile); + + m_TimeBtTrainInAndOut = m_Script.GetNextDouble(); + m_TimeBtTrainIterations = m_Script.GetNextDouble(); + m_TimeBtTestIterations = m_Script.GetNextDouble(); + m_TotalTrainingIterations = m_Script.GetNextInt(); + m_TotalTestingIterations = m_Script.GetNextInt(); + m_TimeBtStartMsgAndInput = m_Script.GetNextDouble(); + m_TimeToWaitForTestResponse = m_Script.GetNextDouble(); + m_TimeBtOutputAndEndMsg = m_Script.GetNextDouble(); + + m_Input = m_Script.GetNextString(); + m_ExpectedOutput = m_Script.GetNextString(); + m_NumRepeats = m_Script.GetNextInt(); + + Wait(m_TimeBtTrainIterations); +} +*/ + +trainer::~trainer() +{ + if(bci_input_) + delete bci_input_; + + if(bci_output_) + delete bci_output_; + + if(bci_output_trainer_) + delete bci_output_trainer_; +} + +void trainer::wait(double duration) +{ + waiting_ = true; + wait_start_ = current_time_; + wait_duration_ = duration; +} + +bool trainer::is_completed() +{ + return state_ == training_state::completed; +} + +//I guess this is a finite state machine? +//Maybe there's a better way to do it, but this made sense +void trainer::update(double /*time*/) +{ +/* + double last = m_CurrentTime; + + m_CurrentTime = time; + m_DeltaTime = m_CurrentTime - last; + + std::string output = ""; + + if(m_Waiting) + { + if(m_State == TestWaitForOutput) + { + output = m_BciOutput->get_state(); + + if(output != "") + { + m_Waiting = false; + } + } + + if(m_CurrentTime >= m_WaitStart + m_WaitDuration) + { + m_Waiting = false; + } + } + + if(!m_Waiting) + { + switch(m_State) + { + case TeachStartMsg: + { + //m_BciInput->SetState("StartTeach"); + m_State = TeachSetInput; + + Wait(m_TimeBtStartMsgAndInput); + + break; + } + + case TeachSetInput: + { + m_BciInput->set_state(m_Input); + std::printf("Taught Input: %s\n", m_Input.c_str()); + m_State = TeachSetOutput; + + Wait(m_TimeBtTrainInAndOut); + + break; + } + + case TeachSetOutput: + { + m_BciOutputTrainer->set_state(m_ExpectedOutput); + std::printf("Taught Output: %s\n", m_ExpectedOutput.c_str()); + m_State = TeachEndMsg; + + Wait(m_TimeBtOutputAndEndMsg); + + break; + } + + case TeachEndMsg: + { + //m_BciInput->set_state("EndTeach"); + + if(++m_CurrentRepeats == m_NumRepeats) + { + if(++m_CurrTrainingIteration == m_TotalTrainingIterations) + { + m_State = TestStartMsg; + m_CurrTestingIteration = 0; + + Wait(m_TimeBtTestIterations); + + break; + } + + m_Input = m_Script.GetNextString(); + m_ExpectedOutput = m_Script.GetNextString(); + m_NumRepeats = m_Script.GetNextInt(); + + m_CurrentRepeats = 0; + } + + m_State = TeachStartMsg; + + Wait(m_TimeBtTrainIterations); + + break; + } + + case TestStartMsg: + { + //m_BciInput->set_state("StartTest"); + m_State = TestSetInput; + + Wait(m_TimeBtStartMsgAndInput); + break; + } + + case TestSetInput: + { + m_Input = m_Script.GetNextString(); + m_ExpectedOutput = m_Script.GetNextString(); + + m_BciInput->set_state(m_Input); + std::printf("Testing Input: %s\n", m_Input.c_str()); + + m_State = TestWaitForOutput; + + Wait(m_TimeToWaitForTestResponse); + + break; + } + + case TestWaitForOutput: + { + std::printf("Received Response: %s (Correct Response: %s)\n", output.c_str(),m_ExpectedOutput.c_str()); + + m_BciOutput->get_neuron(m_ExpectedOutput)->BackpropagateUp(); + if(output != "" && output != m_ExpectedOutput) + { + m_BciOutput->GetNeuron(output)->BackpropagateDown(); + std::printf("Bad\n"); + //m_BciReward->set_state("Good"); + } + else + { + std::printf("Good\n"); + } + + if(output == m_ExpectedOutput) + { + m_BciOutput->GetNeuron(output)->BackpropagateUp(); + std::printf("Good\n"); + //m_BciReward->set_state("Good"); + } + else + { + if(output != "") + m_BciOutput->GetNeuron(output)->BackpropagateDown(); + else + { + for(std::map::iterator i = m_BciOutput->m_SynapseCommandMap.begin(); i != m_BciOutput->m_SynapseCommandMap.end(); i++) + { + m_BciOutput->GetNeuron(i->first)->BackpropagateUp(); + } + } + std::printf("Bad\n"); + //m_BciReward->set_state("Bad"); + } + + m_State = TestEndMsg; + + Wait(m_TimeBtOutputAndEndMsg); + + break; + } + + case TestEndMsg: + { + //m_BciInput->set_state("EndTest"); + + if(++m_CurrTestingIteration == m_TotalTestingIterations) + { + std::printf("Trainer Finished\n"); + m_State = Finished; + + Wait(m_TimeBtTestIterations); + + break; + } + + m_State = TestStartMsg; + + Wait(m_TimeBtTestIterations); + + break; + } + + case Finished: + default: + { + break; + } + } + } +*/ +} + +layer *trainer::get_layer(const std::string &name) +{ + if(name == "input") + { + return bci_input_->get_layer(); + } + else if(name == "output") + { + return bci_output_->get_layer(); + } + else if(name == "outputTrainer") + { + return bci_output_trainer_->get_layer(); + } + + return 0; +} + +void trainer::reset() +{ + +} + +void trainer::print_state() +{ + +} + +} // namespace cort diff --git a/source/simulation/trainer.hpp b/source/simulation/trainer.hpp new file mode 100644 index 0000000..057f1ff --- /dev/null +++ b/source/simulation/trainer.hpp @@ -0,0 +1,102 @@ +/* +The MIT License (MIT) + +Copyright (c) 2006-2016 Thomas Fussell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once +#include + +#include "interface/bci_speech_from_file.hpp" +#include "interface/bci_reward_punishment.hpp" +#include "interface/script.hpp" +#include "simulation/entity.hpp" + +namespace cort { + +class brain; + +enum training_state +{ + uninitialized, + initialized, + teach_start_message, + teach_set_input, + teach_set_output, + teach_end_message, + test_start_message, + test_set_input, + test_attend_output, + test_end_message, + completed +}; + +class trainer : public entity +{ +public: + trainer(); + trainer(const std::string &scriptFilename, brain *brain); + ~trainer(); + + bool is_completed(); + void update(double time); + void reset(); + void print_state(); + layer *get_layer(const std::string &inter); + +private: + void wait(double duration); + + double current_time_; + double delta_time_; + double wait_start_; + double wait_duration_; + bool waiting_; + + int repeat_; + int num_repeats_; + + double time_between_start_message_and_input_; + double time_to_wait_for_test_response_; + double time_between_output_and_end_; + + double time_between_train_in_and_out_; + double time_between_train_iterations_; + double time_between_test_iterations_; + + int total_training_iterations_; + int total_testing_iterations_; + + int training_iteration_; + int testing_iteration_; + + std::string input_; + std::string expected_output_; + + bci_reward_punishment *bci_reward_; + bci_speech_from_file *bci_input_; + bci_speech_from_file *bci_output_; + bci_speech_from_file *bci_output_trainer_; + + script script_; + training_state state_; +}; + +} // namespace cort diff --git a/viewer b/viewer new file mode 160000 index 0000000..857cd88 --- /dev/null +++ b/viewer @@ -0,0 +1 @@ +Subproject commit 857cd886630ca25563dbdccaea6fd46d22f6a532