// Provide classes for simulating a mouse moving in a maze
//
// Copyright 2007 John L Baker. All rights reserved.
//
// This software is provided AS IS under the terms of the Open Source
// MIT License. See http://www.opensource.org/licenses/mit-license.php.
//
// File: maze_baker_2003.cpp
//
// Release: 1.0.0
// Author: John Baker
// Updated: 14 July 2006
//
// Description:
//
// To simulate the effects of NMDA knockout in the CA3 portion of the hippocampus,
// it is necessary to generate inputs similar in form to that which might be present
// for a mouse moving in a maze, typically a Morris water maze or its dry equivalent.
//
// The maze subject is nominally a mouse with motion parameters similar to those
// reported by Nakazawa. Only random swimming (or walking) is provided. Goal
// seeking behavior is not included in the simulation,
//
// The specific maze of interest is circular with landmarks arranged around the
// periphery. Landmarks are specified by their location in a two dimensional
// space. Each landmark is identified through a group of unspecified "features" which
// can be matched with varying degrees of selectivity through a vector dot product.
#include "maze_baker_2003.h"
using namespace std;
using namespace BNSF;
using namespace BAKER_2003;
// --------------------------------------------------------------------
// Landmark class body
// --------------------------------------------------------------------
// Constructors and destructor
Landmark::Landmark()
{
// Set some initial values just to get started
_locX = 0;
_locY = 0;
_category = 0;
}
Landmark::Landmark(Landmark& lm)
{
// Copy each field in the object using appropriate accessors
locX( lm.locX() );
locY( lm.locY() );
category( lm.category() );
features( lm.features() );
}
Landmark::Landmark(
Number x, // Location x coord
Number y, // Location y coord
int cat, // Category
int numFeatEnt, // Number of feature entries
Number features[]) // Feature vector as doubles
{
int k;
_locX = (Number) x;
_locY = (Number) y;
_category = cat;
_features.resize(numFeatEnt);
for (k=0;k<numFeatEnt;k++) {
_features[k] = features[k];
}
}
Landmark::~Landmark() {}
// --------------------------------------------------------------------
// Maze class body
// --------------------------------------------------------------------
// Constructors and destructor
Maze::Maze()
{
// Set initial values
_originX = 0;
_originY = 0;
}
Maze::~Maze()
{
int k;
// Delete held landmarks
for (k=0;k<_landmarks.size(); k++) {
delete _landmarks[k];
}
}
// Add a new landmark using a copy of the one provided
void Maze::add(Landmark* lm)
{
Landmark* newLM = new Landmark(*lm);
_landmarks.push_back(newLM);
}
// Return true if the indicated point is inside the maze.
bool Maze::isInsideMaze(Number locX, Number locY)
{
return boundaryDistance(locX,locY)>=0;
}
// Select points at random from inside the maze, for example, as might
// be used to generate place fields.
void Maze::randomPointInMaze(
Number& x, // X coordinate value (output)
Number& y, // Y coordinate value (output)
UniformRandom* unif) // Source of random numbers
{
Number s = size();
if (unif==NULL) {
unif = UniformRandom::defaultGen();
}
do {
x = originX() + unif->next(-s,s);
y = originY() + unif->next(-s,s);
} while ( !isInsideMaze(x,y) );
}
// Return a location in local coordinates. These are distance measures
// in different directions as appropriate for this location in the maze.
// By default distances are returned using the local orientation in
// north, south, east, and west corresponding to vectors as rotated by
// the local orientation.
void Maze::localCoordinates(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number& northDist, // Boundary dist along (0,1)
Number& southDist, // Boundary dist along (0,-1)
Number& eastDist, // Boundary dist along (1,0)
Number& westDist) // Boundary dist along (-1,0)
{
Number hx,hy;
// Get local orientation as vector (hx,hy)
localOrientation(locX,locY,hx,hy);
// Get relevant distances rotated by the local orientation
northDist = boundaryDistance(locX,locY,hy,hx);
southDist = boundaryDistance(locX,locY,-hy,hx);
eastDist = boundaryDistance(locX,locY,hx,hy);
westDist = boundaryDistance(locX,locY,-hx,-hy);
}
// Provide a heading defined by local conditions in the maze such as
// the direction of the nearest boundary point. The purpose is to provide
// an orientation for a local coordinate system for this part of the maze.
void Maze::localOrientation(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number& hx, // Unit vector x coord (output)
Number& hy) // Unit vector y coord (output)
{
// Return a constant default
hx = 1;
hy = 0;
}
// --------------------------------------------------------------------
// CircularMaze class body
// --------------------------------------------------------------------
CircularMaze::CircularMaze(Number r, Number origX, Number origY)
{
radius(r);
originX(origX);
originY(origY);
}
CircularMaze::~CircularMaze() {}
// Provide a heading defined by local conditions in the maze.
// Value returned is orientation towards nearest boundary point.
void CircularMaze::localOrientation(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number& hx, // Unit vector x coord (output)
Number& hy) // Unit vector y coord (output)
{
Number len = norm(locX,locY);
Number epsilon = 1e-8f;
// Return vector pointing directly to the boundary, which is
// an easy thing to do in a circle unless you are right at the
// center, in which case a vector of (1.0) is returned.
if (len>epsilon) {
hx = (locX-originX() )/len;
hy = (locY-originY() )/len;
}
else {
hx = 1;
hy = 0;
}
}
// Return a location in local coordinates. These are distance measures
// in different directions as appropriate for this location in the maze.
// By default, distances are returned using the local orientation in
// north, south, east, and west corresponding to vectors as rotated by
// the local orientation.
void CircularMaze::localCoordinates(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number& northDist, // Boundary dist along (0,1)
Number& southDist, // Boundary dist along (0,-1)
Number& eastDist, // Boundary dist along (1,0)
Number& westDist) // Boundary dist along (-1,0)
{
Number r = size();
Number len = norm(locX-originX(),locY-originY());
Number sideLenSq = r*r-len*len;
// Distances can be computed directly for a circle. Note that the
// east distance goes negative when (locX,locY) is outside the circle.
eastDist = r-len;
westDist = r+len;
// Make sure (locX,locY) is inside the circle. Otherwise, return a
// negative value for north and south distances.
if (sideLenSq>=0) {
northDist = southDist = sqrt(sideLenSq);
}
else {
// Set distances to a negative value if outside the square.
// This value is not especially meaningful, but goes to zero as
// (locX,locY) approaches the boundary from the outside.
northDist = southDist = -sqrt(-sideLenSq);
}
}
// Distance to the maze wall from a given location
Number CircularMaze::boundaryDistance(Number locX, Number locY)
{
return size()-norm(locX-originX(),locY-originY());
}
// Compute the distance to the maze boundary in a given direction
Number CircularMaze::boundaryDistance(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number hx, // Unit heading vector x coord
Number hy) // Unit heading vector y coord
{
Number x0 = locX-originX();
Number y0 = locY-originY();
Number r = size();
// Determining the distance is solving for d in
// the following simultaneous equations:
//
// d>=0
// x=x0+d*hx
// y=y0+d*hy
// x*x+y*y=r*r
//
// where d is the desired distance. Of course, this is easily done by
// solving a quadratic equation resulting from substituting for x,y.
// We know the solution is real though negative when outside the circle.
Number a = hx*hx+hy*hy; // should = 1, but be safe anyway
Number b = 2*(x0*hx+y0*hy);
Number c = x0*x0+y0*y0-r*r;
Number d = (-b+sqrt(b*b-4*a*c))/(2*a);
return d;
}
// --------------------------------------------------------------------
// RectangularMaze class body
// --------------------------------------------------------------------
RectangularMaze::RectangularMaze(
Number sizex, Number sizey,
Number origx, Number origy)
{
sizeX(sizex);
sizeY(sizey);
originX(origx);
originY(origy);
}
RectangularMaze::~RectangularMaze() {}
// Return a location in local coordinates. These are distance measures
// in different directions as appropriate for this location in the maze.
// By default, distances are returned using the local orientation in
// north, south, east, and west corresponding to vectors as rotated by
// the local orientation.
void RectangularMaze::localCoordinates(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number& northDist, // Boundary dist along (0,1)
Number& southDist, // Boundary dist along (0,-1)
Number& eastDist, // Boundary dist along (1,0)
Number& westDist) // Boundary dist along (-1,0)
{
northDist = originY()+sizeY()-locY;
southDist = locY - originY()+sizeY();
eastDist = originX()+sizeX()-locX;
westDist = locX - originX()+sizeX();
}
// Provide a heading defined by local conditions in the maze.
// Value returned is orientation towards nearest boundary point.
void RectangularMaze::localOrientation(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number& hx, // Unit vector x coord (output)
Number& hy) // Unit vector y coord (output)
{
Number ndist,sdist,edist,wdist;
Number xdist,ydist;
localCoordinates(locX,locY,ndist,sdist,edist,wdist);
xdist = minval(edist,wdist);
ydist = minval(ndist,sdist);
if (xdist<=ydist) {
hx = edist<=wdist ? 1 : -1;
hy =0;
}
else {
hx = 0;
hy = ndist<=sdist ? 1 : -1;
}
}
// Provide minimum distance to the maze wall from a given location.
// Return a negative value if the current location is outside the maze.
Number RectangularMaze::boundaryDistance(
Number locX, // Current location X coord
Number locY) // Current location Y coord
{
Number ndist,sdist,edist,wdist;
Number xdist,ydist;
localCoordinates(locX,locY,ndist,sdist,edist,wdist);
xdist = minval(edist,wdist);
ydist = minval(ndist,sdist);
return minval(xdist,ydist);
}
// Provide the distance from maze wall along a unit vector.
// Return a negative value if the current location is outside the maze.
Number RectangularMaze::boundaryDistance(
Number locX, // Current location X coord
Number locY, // Current location Y coord
Number hx, // Unit vector x coord
Number hy) // Unit vector y coord
{
Number ndist,sdist,edist,wdist;
Number sx,sy;
localCoordinates(locX,locY,ndist,sdist,edist,wdist);
// S is the distance along (hx,hy) until the boundary
// is hit. sx is the x constraint and sy the y constaint.
if (hx>0) sx= edist/hx;
else if (hx<0) sx=-wdist/hx;
else sx=size();
if (hy>0) sy= ndist/hy;
else if (hy<0) sy=-sdist/hy;
else sy=size();
return minval(sx,sy);
}
// --------------------------------------------------------------------
// RectangularRegion class body
// --------------------------------------------------------------------
// Constructor and destructor
RectangularRegion::RectangularRegion(
Number xmin,
Number xmax,
Number ymin,
Number ymax)
{
_xmin = xmin;
_xmax = xmax;
_ymin = ymin;
_ymax = ymax;
}
RectangularRegion::~RectangularRegion() {}
// Get the bounding box of the region
void RectangularRegion::getBoundingBox(
Number& xmin,
Number& xmax,
Number& ymin,
Number& ymax)
{
xmin = _xmin;
xmax = _xmax;
ymin = _ymin;
ymax = _ymax;
}
// Set the bounding box of the region
void RectangularRegion::setBoundingBox(
Number xmin,
Number xmax,
Number ymin,
Number ymax)
{
_xmin = xmin;
_xmax = xmax;
_ymin = ymin;
_ymax = ymax;
}
// Return true if the point supplied is in the region
bool RectangularRegion::containsPoint(
Number x,
Number y)
{
return
x >= _xmin &&
x <= _xmax &&
y >= _ymin &&
y <= _ymax;
}
// --------------------------------------------------------------------
// CircularRegion class body
// --------------------------------------------------------------------
// Constructor and destructor
CircularRegion::CircularRegion(Number xctr, Number yctr, Number r)
{
_centerX = xctr;
_centerY = yctr;
_radius = r;
}
CircularRegion::~CircularRegion() {}
// Get the bounding box of the region
void CircularRegion::getBoundingBox(
Number& xmin,
Number& xmax,
Number& ymin,
Number& ymax)
{
xmin = centerX()-radius();
xmax = centerX()+radius();
ymin = centerY()-radius();
ymax = centerY()+radius();
}
// Return true if the point supplied is in the region
bool CircularRegion::containsPoint(
Number x,
Number y)
{
Number dx = x-centerX();
Number dy = y-centerY();
return (dx*dx+dy*dy)<=radius()*radius();
}