<commit_before>/*
 * Copyright (C) 2017-2018 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use std::cmp;

use model::bitboard::{BitBoard, BitBoardIter};
use model::constants::*;
use model::zobrist::ZobristHash;
use model::{Color, ColorMap, FieldCoord, HexCoord, Move};

#[derive(Clone, Copy, PartialEq)]
pub struct Board {
    /*
    Board layout:
    The hex board uses an axial coordinate system with (0, 0) at the center. The x-axis slopes
    up and to the right, and the y-axis goes up and down.
    See http://www.redblobgames.com/grids/hexagons/#coordinates-axial for more info.
                                             ____
                                            /    \
                                       ____/  +y  \____
                                      /    \      /    \
                                     /      \____/  +x  \
                                     \      /    \      /
                                      \____/      \____/
                                      /    \      /    \
                                     /  -x  \____/      \
                                     \      /    \      /
                                      \____/  -y  \____/
                                           \      /
                                            \____/

    Field layout:
    Fields are numbered clockwise from the top. Even indicies are black, odd indicies are white.
                                          _________
                                         / \     / \
                                        /   \ 0 /   \
                                       /  5  \ /  1  \
                                      (-------*-------)
                                       \  4  / \  2  /
                                        \   / 3 \   /
                                         \_/_____\_/
    u64 Bitboard layout:

     MSB                                                                              LSB

     7 bits                            57 bits (19 groups of 3)
     +-----+  +-------------------------------------------------------------------------+
    [0000000][000 111 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 010 000]
     Unused   -+- -+-                 ||+-- Field 0           ||+-- Field 1       |+-- Piece on field
               |   +-- Extant hex     |+--- Field 2           |+--- Field 3       +--- No piece on field
               +------ Removed hex    +---- Field 4           +---- Field 5            (Field bitboards)
                      (Hex bitboard)  (Black field bitboard)  (White field bitboard)
    */
    fields: ColorMap<BitBoard>,
    hexes: BitBoard,
    extant_hex_count: u8,
    turn: Color,
    vitals: ColorMap<PlayerVitals>,
    outcome: Outcome,
    zobrist: ZobristHash,
}

/// A struct tracking a player's piece and captured hex count. So named because these two numbers are
/// essential to a player's survival (i.e. vital signs).
#[derive(Clone, Copy, PartialEq)]
pub struct PlayerVitals {
    pieces: u8,
    hexes: u8,
}

impl PlayerVitals {
    fn new() -> Self {
        Self {
            pieces: 18,
            hexes: 0,
        }
    }
}

/// The outcome of a game. Wins or draws caused by a resignation or an offered and accepted draw are
/// not differentiated from wins and draws by capturing all of an opponent's pieces, running out of
/// moves, being unable to capture any of the opponent's pieces, or reaching threefold repetition.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Outcome {
    InProgress,
    Win(Color),
    Draw,
}

// Public methods
impl Board {
    /// Create a new board with the "Laurentius" starting position.
    pub fn new() -> Self {
        let fields = generate_laurentius();

        Self {
            fields,
            hexes: HEX_STARTING_POSITION,
            extant_hex_count: 19,
            turn: Color::White,
            vitals: ColorMap::new(PlayerVitals::new(), PlayerVitals::new()),
            outcome: Outcome::InProgress,
            zobrist: ZobristHash::new(fields, ColorMap::new(0, 0), Color::White),
        }
    }
    pub fn apply_move(&mut self, mv: &Move) {
        assert!(self.can_apply_move(mv), "Cannot apply {:?}", mv);
        match *mv {
            Move::Move(from, to, color) => {
                self.toggle_field(from | to, color);

                self.zobrist.toggle_field(from, color);
                self.zobrist.toggle_field(to, color);

                let (capture_count, mut fields_to_check) =
                    self.check_hexes(from.trailing_zeros() as usize / 3);
                fields_to_check |= EDGE_NEIGHBORS.bb_get(to, color);
                self.check_captures(fields_to_check);

                if capture_count != 0 {
                    let vitals = self.vitals.get_mut(self.turn);
                    self.zobrist
                        .set_hex_count(vitals.hexes, vitals.hexes + capture_count, color);
                    vitals.hexes += capture_count;
                }
            }
            Move::Exchange(bb, color) => {
                self.remove_piece(bb, color);

                {
                    let vitals = self.vitals.get_mut(self.turn);
                    self.zobrist
                        .set_hex_count(vitals.hexes, vitals.hexes - 2, color);
                    vitals.hexes -= 2;
                }

                // Players don't collect hexes removed due to an exchange
                let (_, fields_to_check) = self.check_hexes(bb.trailing_zeros() as usize / 3);
                self.check_captures(fields_to_check);
            }
        }
        self.turn = self.turn.switch();
        self.zobrist.switch_turn();
        self.update_outcome();
    }
    pub fn can_apply_move(&self, mv: &Move) -> bool {
        match *mv {
            Move::Move(from, to, color) => {
                let vertex_neighbors = VERTEX_NEIGHBORS.bb_get(from, color);
                color == self.turn
                    && (to & vertex_neighbors != 0)
                    && self.is_piece_on_bitboard(from, color)
                    && !self.is_piece_on_bitboard(to, color)
            }
            Move::Exchange(bb, color) => {
                self.can_exchange() && color != self.turn && self.is_piece_on_bitboard(bb, color)
            }
        }
    }
    pub fn generate_moves(&self) -> impl Iterator<Item = Move> {
        let turn = self.turn;
        let fields = self.fields.get(turn);

        assert_ne!(fields, 0);

        let hexes = self.hexes;
        let opp_color = turn.switch();
        let opp_fields = if self.can_exchange() {
            self.fields.get(opp_color)
        } else {
            // impl Trait requires that we return a single, concrete type. So, if there are no
            // fields to exchange, we create an empty BitBoardIter and chain it on anyways. This
            // way, the type of the resulting iterator is always Chain.
            0
        };

        BitBoardIter::new(fields)
            .flat_map(move |origin| {
                let vertex_neighbors = VERTEX_NEIGHBORS.bb_get(origin, turn) & (!fields & hexes);
                BitBoardIter::new(vertex_neighbors).map(move |dest| Move::Move(origin, dest, turn))
            })
            .chain(
                BitBoardIter::new(opp_fields)
                    .map(move |exchanged| Move::Exchange(exchanged, opp_color)),
            )
    }
    pub fn available_moves_for_piece(&self, field: &FieldCoord) -> Vec<FieldCoord> {
        if self.is_piece_on_field(field) {
            let color = field.color();
            let vertex_neighbors = self.hexes & VERTEX_NEIGHBORS.bb_get(field.to_bitboard(), color);
            let mut moves = Vec::with_capacity(3);

            for dest in BitBoardIter::new(vertex_neighbors) {
                moves.push(FieldCoord::from_bitboard(dest, color));
            }
            moves
        } else {
            vec![]
        }
    }
    pub fn can_exchange(&self) -> bool {
        self.vitals.get(self.turn).hexes >= 2
    }
    pub fn is_piece_on_field(&self, coord: &FieldCoord) -> bool {
        self.is_piece_on_bitboard(coord.to_bitboard(), coord.color())
    }
    pub fn is_piece_on_bitboard(&self, bb: BitBoard, color: Color) -> bool {
        assert!(
            bb & self.hexes != 0,
            "Cannot cannot check if piece is on {:?}. Hex was removed.",
            FieldCoord::from_bitboard(bb, color),
        );

        bb & self.fields.get(color) != 0
    }
    /// > extant (adj.): Still in existence; not destroyed, lost, or extinct (The Free Dictionary)
    ///
    /// Return the coordinates of the hexes that have not been removed yet.
    pub fn extant_hexes(&self) -> Vec<HexCoord> {
        let mut coords = Vec::with_capacity(19);
        let try_coord = |coords: &mut Vec<HexCoord>, x, y| {
            if let Some(hex) = self.try_hex((x, y)) {
                coords.push(hex);
            }
        };
        try_coord(&mut coords, 1, 1);
        try_coord(&mut coords, -1, -1);

        for x in 0..3 {
            for y in -2..1 {
                try_coord(&mut coords, x, y);
                if x != y {
                    try_coord(&mut coords, -x, -y);
                }
            }
        }
        coords
    }
    /// > extant (adj.): Still in existence; not destroyed, lost, or extinct (The Free Dictionary)
    ///
    /// Returns true if a hex has not been removed yet.
    pub fn is_hex_extant(&self, index: usize) -> bool {
        self.hexes & HEX_MASK[index] != 0
    }
    pub fn resign(&mut self) {
        assert_eq!(self.outcome, Outcome::InProgress);
        self.outcome = Outcome::Win(self.turn.switch());
    }
    pub fn turn(&self) -> Color {
        self.turn
    }
    pub fn pieces(&self, color: Color) -> u8 {
        self.vitals.get(color).pieces
    }
    pub fn hexes(&self, color: Color) -> u8 {
        self.vitals.get(color).hexes
    }
    pub fn vitals(&self) -> ColorMap<PlayerVitals> {
        self.vitals
    }
    pub fn outcome(&self) -> Outcome {
        self.outcome
    }
    pub fn zobrist(&self) -> ZobristHash {
        self.zobrist
    }
}

impl Board {
    // TODO: check for threefold repetition
    fn update_outcome(&mut self) {
        let fields = self.fields.get(self.turn);

        if fields == 0 {
            // No more pieces left
            self.outcome = Outcome::Win(self.turn.switch());
        } else if fields == self.hexes && !self.can_exchange() {
            // There are no empty fields to move to and we can't exchange
            self.outcome = Outcome::Draw;
        } else {
            use model::Color::*;

            let wp = self.pieces(White);
            let bp = self.pieces(Black);
            let wh = self.hexes(White);
            let bh = self.hexes(Black);

            // If neither side can capture the other's pieces, the game is drawn
            if wp == 1 && bp == 1 && (self.extant_hex_count + cmp::max(wh, bh) - 1 < 2) {
                self.outcome = Outcome::Draw;
            }
        }
    }
}

// Field and piece methods
impl Board {
    fn toggle_field(&mut self, bb: BitBoard, color: Color) {
        assert_ne!(
            bb & self.hexes,
            0,
            "Trying to toggle field(s) on removed hex(es)"
        );

        *self.fields.get_mut(color) ^= bb;
    }
    fn remove_piece(&mut self, bb: BitBoard, color: Color) {
        assert!(
            self.is_piece_on_bitboard(bb, color),
            "There is no piece at {:?} to remove",
            FieldCoord::from_bitboard(bb, color)
        );
        self.toggle_field(bb, color);
        self.vitals.get_mut(color).pieces -= 1;
    }
    fn check_captures(&mut self, mut fields_to_check: BitBoard) {
        // fields_to_check must be a BitBoard for the opponent player (i.e. opposite of current turn)
        let us = self.turn;
        let them = us.switch();
        fields_to_check &= self.hexes & self.fields.get(them);
        for bb in BitBoardIter::new(fields_to_check) {
            let neighbors = self.hexes & EDGE_NEIGHBORS.bb_get(bb, them);
            if !self.fields.get(us) & neighbors == 0 {
                self.remove_piece(bb, them);
            }
        }
    }
}

// Hex methods
impl Board {
    /// A hex is removable (and must be removed) if it is empty and is "attached to the board by 3
    /// or less adjacent sides."
    fn is_hex_removable(&self, index: usize) -> bool {
        debug_assert!(self.is_hex_extant(index));

        if (self.fields.white | self.fields.black) & HEX_MASK[index] != 0 {
            return false;
        }

        // Combining colors here is okay because there won't be overlaps
        let hex = self.hexes
            & (HEX_FIELD_NEIGHBORS.index_get(index, Color::White)
                | HEX_FIELD_NEIGHBORS.index_get(index, Color::Black));
        // There are 18 combinations to check for each hex
        REMOVABLE_HEX_COMBS
            .iter()
            .skip(index * 18)
            .take(18)
            .any(|&comb| hex == comb)
    }
    fn remove_hex(&mut self, index: usize) -> bool {
        let removable = self.is_hex_removable(index);

        if removable {
            self.hexes &= !HEX_MASK[index];
            self.extant_hex_count -= 1;
        }
        removable
    }
    fn try_hex(&self, coord: (i8, i8)) -> Option<HexCoord> {
        if let Some(coord) = HexCoord::try_new(coord.0, coord.1) {
            if self.is_hex_extant(coord.to_index()) {
                return Some(coord);
            }
        }
        None
    }
    fn check_hexes(&mut self, index: usize) -> (u8, BitBoard) {
        let mut remove_count = 0;
        let mut fields = 0;

        if self.remove_hex(index) {
            remove_count += 1;

            let our_neighbors = self.hexes & HEX_FIELD_NEIGHBORS.index_get(index, self.turn);
            let their_neighbors =
                self.hexes & HEX_FIELD_NEIGHBORS.index_get(index, self.turn.switch());

            for neighbor in BitBoardIter::new(our_neighbors | their_neighbors) {
                let check_result = self.check_hexes(neighbor.trailing_zeros() as usize / 3);
                remove_count += check_result.0;
                fields |= check_result.1;
            }

            // Add in the opponent's neighbors, excluding those on hexes that have been removed
            fields |= their_neighbors;
            fields &= self.hexes;
        }
        (remove_count, fields)
    }
}
<commit_msg>Clarify variable name<commit_after>/*
 * Copyright (C) 2017-2018 Ryan Huang
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use std::cmp;

use model::bitboard::{BitBoard, BitBoardIter};
use model::constants::*;
use model::zobrist::ZobristHash;
use model::{Color, ColorMap, FieldCoord, HexCoord, Move};

#[derive(Clone, Copy, PartialEq)]
pub struct Board {
    /*
    Board layout:
    The hex board uses an axial coordinate system with (0, 0) at the center. The x-axis slopes
    up and to the right, and the y-axis goes up and down.
    See http://www.redblobgames.com/grids/hexagons/#coordinates-axial for more info.
                                             ____
                                            /    \
                                       ____/  +y  \____
                                      /    \      /    \
                                     /      \____/  +x  \
                                     \      /    \      /
                                      \____/      \____/
                                      /    \      /    \
                                     /  -x  \____/      \
                                     \      /    \      /
                                      \____/  -y  \____/
                                           \      /
                                            \____/

    Field layout:
    Fields are numbered clockwise from the top. Even indicies are black, odd indicies are white.
                                          _________
                                         / \     / \
                                        /   \ 0 /   \
                                       /  5  \ /  1  \
                                      (-------*-------)
                                       \  4  / \  2  /
                                        \   / 3 \   /
                                         \_/_____\_/
    u64 Bitboard layout:

     MSB                                                                              LSB

     7 bits                            57 bits (19 groups of 3)
     +-----+  +-------------------------------------------------------------------------+
    [0000000][000 111 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 010 000]
     Unused   -+- -+-                 ||+-- Field 0           ||+-- Field 1       |+-- Piece on field
               |   +-- Extant hex     |+--- Field 2           |+--- Field 3       +--- No piece on field
               +------ Removed hex    +---- Field 4           +---- Field 5            (Field bitboards)
                      (Hex bitboard)  (Black field bitboard)  (White field bitboard)
    */
    fields: ColorMap<BitBoard>,
    hexes: BitBoard,
    extant_hex_count: u8,
    turn: Color,
    vitals: ColorMap<PlayerVitals>,
    outcome: Outcome,
    zobrist: ZobristHash,
}

/// A struct tracking a player's piece and captured hex count. So named because these two numbers are
/// essential to a player's survival (i.e. vital signs).
#[derive(Clone, Copy, PartialEq)]
pub struct PlayerVitals {
    pieces: u8,
    hexes: u8,
}

impl PlayerVitals {
    fn new() -> Self {
        Self {
            pieces: 18,
            hexes: 0,
        }
    }
}

/// The outcome of a game. Wins or draws caused by a resignation or an offered and accepted draw are
/// not differentiated from wins and draws by capturing all of an opponent's pieces, running out of
/// moves, being unable to capture any of the opponent's pieces, or reaching threefold repetition.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Outcome {
    InProgress,
    Win(Color),
    Draw,
}

// Public methods
impl Board {
    /// Create a new board with the "Laurentius" starting position.
    pub fn new() -> Self {
        let fields = generate_laurentius();

        Self {
            fields,
            hexes: HEX_STARTING_POSITION,
            extant_hex_count: 19,
            turn: Color::White,
            vitals: ColorMap::new(PlayerVitals::new(), PlayerVitals::new()),
            outcome: Outcome::InProgress,
            zobrist: ZobristHash::new(fields, ColorMap::new(0, 0), Color::White),
        }
    }
    pub fn apply_move(&mut self, mv: &Move) {
        assert!(self.can_apply_move(mv), "Cannot apply {:?}", mv);
        match *mv {
            Move::Move(from, to, color) => {
                self.toggle_field(from | to, color);

                self.zobrist.toggle_field(from, color);
                self.zobrist.toggle_field(to, color);

                let (capture_count, mut fields_to_check) =
                    self.check_hexes(from.trailing_zeros() as usize / 3);
                fields_to_check |= EDGE_NEIGHBORS.bb_get(to, color);
                self.check_captures(fields_to_check);

                if capture_count != 0 {
                    let vitals = self.vitals.get_mut(self.turn);
                    self.zobrist
                        .set_hex_count(vitals.hexes, vitals.hexes + capture_count, color);
                    vitals.hexes += capture_count;
                }
            }
            Move::Exchange(bb, color) => {
                self.remove_piece(bb, color);

                {
                    let vitals = self.vitals.get_mut(self.turn);
                    self.zobrist
                        .set_hex_count(vitals.hexes, vitals.hexes - 2, color);
                    vitals.hexes -= 2;
                }

                // Players don't collect hexes removed due to an exchange
                let (_, fields_to_check) = self.check_hexes(bb.trailing_zeros() as usize / 3);
                self.check_captures(fields_to_check);
            }
        }
        self.turn = self.turn.switch();
        self.zobrist.switch_turn();
        self.update_outcome();
    }
    pub fn can_apply_move(&self, mv: &Move) -> bool {
        match *mv {
            Move::Move(from, to, color) => {
                let vertex_neighbors = VERTEX_NEIGHBORS.bb_get(from, color);
                color == self.turn
                    && (to & vertex_neighbors != 0)
                    && self.is_piece_on_bitboard(from, color)
                    && !self.is_piece_on_bitboard(to, color)
            }
            Move::Exchange(bb, color) => {
                self.can_exchange() && color != self.turn && self.is_piece_on_bitboard(bb, color)
            }
        }
    }
    pub fn generate_moves(&self) -> impl Iterator<Item = Move> {
        let turn = self.turn;
        let fields = self.fields.get(turn);

        assert_ne!(fields, 0);

        let hexes = self.hexes;
        let opp_color = turn.switch();
        let opp_fields = if self.can_exchange() {
            self.fields.get(opp_color)
        } else {
            // impl Trait requires that we return a single, concrete type. So, if there are no
            // fields to exchange, we create an empty BitBoardIter and chain it on anyways. This
            // way, the type of the resulting iterator is always Chain.
            0
        };

        BitBoardIter::new(fields)
            .flat_map(move |origin| {
                let empty_vertex_neighbors =
                    VERTEX_NEIGHBORS.bb_get(origin, turn) & (!fields & hexes);
                BitBoardIter::new(empty_vertex_neighbors)
                    .map(move |dest| Move::Move(origin, dest, turn))
            })
            .chain(
                BitBoardIter::new(opp_fields)
                    .map(move |exchanged| Move::Exchange(exchanged, opp_color)),
            )
    }
    pub fn available_moves_for_piece(&self, field: &FieldCoord) -> Vec<FieldCoord> {
        if self.is_piece_on_field(field) {
            let color = field.color();
            let vertex_neighbors = self.hexes & VERTEX_NEIGHBORS.bb_get(field.to_bitboard(), color);
            let mut moves = Vec::with_capacity(3);

            for dest in BitBoardIter::new(vertex_neighbors) {
                moves.push(FieldCoord::from_bitboard(dest, color));
            }
            moves
        } else {
            vec![]
        }
    }
    pub fn can_exchange(&self) -> bool {
        self.vitals.get(self.turn).hexes >= 2
    }
    pub fn is_piece_on_field(&self, coord: &FieldCoord) -> bool {
        self.is_piece_on_bitboard(coord.to_bitboard(), coord.color())
    }
    pub fn is_piece_on_bitboard(&self, bb: BitBoard, color: Color) -> bool {
        assert!(
            bb & self.hexes != 0,
            "Cannot cannot check if piece is on {:?}. Hex was removed.",
            FieldCoord::from_bitboard(bb, color),
        );

        bb & self.fields.get(color) != 0
    }
    /// > extant (adj.): Still in existence; not destroyed, lost, or extinct (The Free Dictionary)
    ///
    /// Return the coordinates of the hexes that have not been removed yet.
    pub fn extant_hexes(&self) -> Vec<HexCoord> {
        let mut coords = Vec::with_capacity(19);
        let try_coord = |coords: &mut Vec<HexCoord>, x, y| {
            if let Some(hex) = self.try_hex((x, y)) {
                coords.push(hex);
            }
        };
        try_coord(&mut coords, 1, 1);
        try_coord(&mut coords, -1, -1);

        for x in 0..3 {
            for y in -2..1 {
                try_coord(&mut coords, x, y);
                if x != y {
                    try_coord(&mut coords, -x, -y);
                }
            }
        }
        coords
    }
    /// > extant (adj.): Still in existence; not destroyed, lost, or extinct (The Free Dictionary)
    ///
    /// Returns true if a hex has not been removed yet.
    pub fn is_hex_extant(&self, index: usize) -> bool {
        self.hexes & HEX_MASK[index] != 0
    }
    pub fn resign(&mut self) {
        assert_eq!(self.outcome, Outcome::InProgress);
        self.outcome = Outcome::Win(self.turn.switch());
    }
    pub fn turn(&self) -> Color {
        self.turn
    }
    pub fn pieces(&self, color: Color) -> u8 {
        self.vitals.get(color).pieces
    }
    pub fn hexes(&self, color: Color) -> u8 {
        self.vitals.get(color).hexes
    }
    pub fn vitals(&self) -> ColorMap<PlayerVitals> {
        self.vitals
    }
    pub fn outcome(&self) -> Outcome {
        self.outcome
    }
    pub fn zobrist(&self) -> ZobristHash {
        self.zobrist
    }
}

impl Board {
    // TODO: check for threefold repetition
    fn update_outcome(&mut self) {
        let fields = self.fields.get(self.turn);

        if fields == 0 {
            // No more pieces left
            self.outcome = Outcome::Win(self.turn.switch());
        } else if fields == self.hexes && !self.can_exchange() {
            // There are no empty fields to move to and we can't exchange
            self.outcome = Outcome::Draw;
        } else {
            use model::Color::*;

            let wp = self.pieces(White);
            let bp = self.pieces(Black);
            let wh = self.hexes(White);
            let bh = self.hexes(Black);

            // If neither side can capture the other's pieces, the game is drawn
            if wp == 1 && bp == 1 && (self.extant_hex_count + cmp::max(wh, bh) - 1 < 2) {
                self.outcome = Outcome::Draw;
            }
        }
    }
}

// Field and piece methods
impl Board {
    fn toggle_field(&mut self, bb: BitBoard, color: Color) {
        assert_ne!(
            bb & self.hexes,
            0,
            "Trying to toggle field(s) on removed hex(es)"
        );

        *self.fields.get_mut(color) ^= bb;
    }
    fn remove_piece(&mut self, bb: BitBoard, color: Color) {
        assert!(
            self.is_piece_on_bitboard(bb, color),
            "There is no piece at {:?} to remove",
            FieldCoord::from_bitboard(bb, color)
        );
        self.toggle_field(bb, color);
        self.vitals.get_mut(color).pieces -= 1;
    }
    fn check_captures(&mut self, mut fields_to_check: BitBoard) {
        // fields_to_check must be a BitBoard for the opponent player (i.e. opposite of current turn)
        let us = self.turn;
        let them = us.switch();
        fields_to_check &= self.hexes & self.fields.get(them);
        for bb in BitBoardIter::new(fields_to_check) {
            let neighbors = self.hexes & EDGE_NEIGHBORS.bb_get(bb, them);
            if !self.fields.get(us) & neighbors == 0 {
                self.remove_piece(bb, them);
            }
        }
    }
}

// Hex methods
impl Board {
    /// A hex is removable (and must be removed) if it is empty and is "attached to the board by 3
    /// or less adjacent sides."
    fn is_hex_removable(&self, index: usize) -> bool {
        debug_assert!(self.is_hex_extant(index));

        if (self.fields.white | self.fields.black) & HEX_MASK[index] != 0 {
            return false;
        }

        // Combining colors here is okay because there won't be overlaps
        let hex = self.hexes
            & (HEX_FIELD_NEIGHBORS.index_get(index, Color::White)
                | HEX_FIELD_NEIGHBORS.index_get(index, Color::Black));
        // There are 18 combinations to check for each hex
        REMOVABLE_HEX_COMBS
            .iter()
            .skip(index * 18)
            .take(18)
            .any(|&comb| hex == comb)
    }
    fn remove_hex(&mut self, index: usize) -> bool {
        let removable = self.is_hex_removable(index);

        if removable {
            self.hexes &= !HEX_MASK[index];
            self.extant_hex_count -= 1;
        }
        removable
    }
    fn try_hex(&self, coord: (i8, i8)) -> Option<HexCoord> {
        if let Some(coord) = HexCoord::try_new(coord.0, coord.1) {
            if self.is_hex_extant(coord.to_index()) {
                return Some(coord);
            }
        }
        None
    }
    fn check_hexes(&mut self, index: usize) -> (u8, BitBoard) {
        let mut remove_count = 0;
        let mut fields = 0;

        if self.remove_hex(index) {
            remove_count += 1;

            let our_neighbors = self.hexes & HEX_FIELD_NEIGHBORS.index_get(index, self.turn);
            let their_neighbors =
                self.hexes & HEX_FIELD_NEIGHBORS.index_get(index, self.turn.switch());

            for neighbor in BitBoardIter::new(our_neighbors | their_neighbors) {
                let check_result = self.check_hexes(neighbor.trailing_zeros() as usize / 3);
                remove_count += check_result.0;
                fields |= check_result.1;
            }

            // Add in the opponent's neighbors, excluding those on hexes that have been removed
            fields |= their_neighbors;
            fields &= self.hexes;
        }
        (remove_count, fields)
    }
}
<|endoftext|>


<commit_before>use super::{BitBoard, MAX_SIZE, MIN_SIZE, Point, Side, Size};
use super::multi_direction::{MdMask, MdOffset};
use std::cmp;

#[derive(Copy, Clone, Debug)]
pub struct Board {
    size: Size,
    turn: Option<Side>,
    offset: MdOffset,
    black_cells: BitBoard,
    white_cells: BitBoard,
    move_cand: BitBoard,
}

impl Board {
    pub fn new(size: Size) -> Self {
        assert!(MIN_SIZE <= size.0 && size.0 <= MAX_SIZE);
        assert!(MIN_SIZE <= size.1 && size.1 <= MAX_SIZE);

        let (x, y) = (size.0 / 2 - 1, size.1 / 2 - 1);
        let mut board = Board {
            size: size,
            turn: Some(Side::Black),
            offset: MdOffset::from_size(size),
            black_cells: BitBoard::from_point(Point(x, y), size) |
                         BitBoard::from_point(Point(x + 1, y + 1), size),
            white_cells: BitBoard::from_point(Point(x + 1, y), size) |
                         BitBoard::from_point(Point(x, y + 1), size),
            move_cand: BitBoard::empty(),
        };
        board.move_cand = board.compute_move_cand();
        board
    }

    pub fn size(&self) -> Size {
        self.size
    }

    pub fn turn(&self) -> Option<Side> {
        self.turn
    }

    pub fn black_cells(&self) -> BitBoard {
        self.black_cells
    }

    pub fn white_cells(&self) -> BitBoard {
        self.white_cells
    }

    pub fn move_candidates(&self) -> BitBoard {
        self.move_cand
    }

    pub fn num_disk(&self, side: Side) -> u32 {
        match side {
            Side::Black => self.black_cells.num_bits(),
            Side::White => self.white_cells.num_bits(),
        }
    }

    pub fn get(&self, pt: Point) -> Option<Side> {
        if self.black_cells.contains(pt, self.size) {
            Some(Side::Black)
        } else if self.white_cells.contains(pt, self.size) {
            Some(Side::White)
        } else {
            None
        }
    }

    pub fn make_move(&self, pt: Point) -> Option<Board> {
        let (turn, flip) = if let Some(tp) = self.flip_disks(pt) {
            tp
        } else {
            return None;
        };

        let mut board = *self;
        match turn {
            Side::Black => {
                board.black_cells |= flip;
                board.white_cells &= !flip;
            }
            Side::White => {
                board.white_cells |= flip;
                board.black_cells &= !flip;
            }
        }

        for &t in &[Some(turn.flip()), Some(turn), None] {
            board.turn = t;
            board.move_cand = board.compute_move_cand();
            if !board.move_cand.is_empty() {
                break;
            }
        }

        Some(board)
    }

    fn flip_disks(&self, pt: Point) -> Option<(Side, BitBoard)> {
        let turn = if let Some(turn) = self.turn {
            turn
        } else {
            return None;
        };

        if !self.move_cand.contains(pt, self.size) {
            return None;
        }

        let (me, you) = match turn {
            Side::Black => (self.black_cells, self.white_cells),
            Side::White => (self.white_cells, self.black_cells),
        };

        let mut flip = BitBoard::from_point(pt, self.size);

        let mut mask = MdMask::new(flip);
        let mut flip_cand = MdMask::new(BitBoard::empty());
        let cnt = cmp::max(cmp::max(pt.0, MAX_SIZE - pt.0 - 1),
                           cmp::max(pt.1, MAX_SIZE - pt.1 - 1));
        for _ in 0..cnt {
            mask = mask.shift(self.offset);
            for (mask, cand) in mask.iter_mut().zip(flip_cand.iter_mut()) {
                if !(*mask & you).is_empty() {
                    *cand |= *mask;
                } else {
                    if !(*mask & me).is_empty() {
                        flip |= *cand;
                    }
                    *mask = BitBoard::empty();
                }
            }
        }

        Some((turn, flip))
    }

    fn compute_move_cand(&self) -> BitBoard {
        let (me, you) = match self.turn {
            Some(Side::Black) => (self.black_cells, self.white_cells),
            Some(Side::White) => (self.white_cells, self.black_cells),
            None => return BitBoard::empty(),
        };

        let mut cand = BitBoard::empty();

        // Search: E Y Y Y M
        let empty = MdMask::new(!me & !you);

        let mut you_mask = MdMask::new(you);
        let mut me_mask = MdMask::new(me).shift(self.offset);
        let mut you_cont_mask = empty;

        for _ in 0..(MAX_SIZE - 2) {
            you_mask = you_mask.shift(self.offset);
            me_mask = me_mask.shift(self.offset);
            you_cont_mask = you_cont_mask & you_mask;

            cand |= (you_cont_mask & me_mask).or_all();
        }

        cand
    }
}
<commit_msg>Fix initial disc position<commit_after>use super::{BitBoard, MAX_SIZE, MIN_SIZE, Point, Side, Size};
use super::multi_direction::{MdMask, MdOffset};
use std::cmp;

#[derive(Copy, Clone, Debug)]
pub struct Board {
    size: Size,
    turn: Option<Side>,
    offset: MdOffset,
    black_cells: BitBoard,
    white_cells: BitBoard,
    move_cand: BitBoard,
}

impl Board {
    pub fn new(size: Size) -> Self {
        assert!(MIN_SIZE <= size.0 && size.0 <= MAX_SIZE);
        assert!(MIN_SIZE <= size.1 && size.1 <= MAX_SIZE);

        let (x, y) = (size.0 / 2 - 1, size.1 / 2 - 1);
        let mut board = Board {
            size: size,
            turn: Some(Side::Black),
            offset: MdOffset::from_size(size),
            black_cells: BitBoard::from_point(Point(x + 1, y), size) |
                         BitBoard::from_point(Point(x, y + 1), size),
            white_cells: BitBoard::from_point(Point(x, y), size) |
                         BitBoard::from_point(Point(x + 1, y + 1), size),
            move_cand: BitBoard::empty(),
        };
        board.move_cand = board.compute_move_cand();
        board
    }

    pub fn size(&self) -> Size {
        self.size
    }

    pub fn turn(&self) -> Option<Side> {
        self.turn
    }

    pub fn black_cells(&self) -> BitBoard {
        self.black_cells
    }

    pub fn white_cells(&self) -> BitBoard {
        self.white_cells
    }

    pub fn move_candidates(&self) -> BitBoard {
        self.move_cand
    }

    pub fn num_disk(&self, side: Side) -> u32 {
        match side {
            Side::Black => self.black_cells.num_bits(),
            Side::White => self.white_cells.num_bits(),
        }
    }

    pub fn get(&self, pt: Point) -> Option<Side> {
        if self.black_cells.contains(pt, self.size) {
            Some(Side::Black)
        } else if self.white_cells.contains(pt, self.size) {
            Some(Side::White)
        } else {
            None
        }
    }

    pub fn make_move(&self, pt: Point) -> Option<Board> {
        let (turn, flip) = if let Some(tp) = self.flip_disks(pt) {
            tp
        } else {
            return None;
        };

        let mut board = *self;
        match turn {
            Side::Black => {
                board.black_cells |= flip;
                board.white_cells &= !flip;
            }
            Side::White => {
                board.white_cells |= flip;
                board.black_cells &= !flip;
            }
        }

        for &t in &[Some(turn.flip()), Some(turn), None] {
            board.turn = t;
            board.move_cand = board.compute_move_cand();
            if !board.move_cand.is_empty() {
                break;
            }
        }

        Some(board)
    }

    fn flip_disks(&self, pt: Point) -> Option<(Side, BitBoard)> {
        let turn = if let Some(turn) = self.turn {
            turn
        } else {
            return None;
        };

        if !self.move_cand.contains(pt, self.size) {
            return None;
        }

        let (me, you) = match turn {
            Side::Black => (self.black_cells, self.white_cells),
            Side::White => (self.white_cells, self.black_cells),
        };

        let mut flip = BitBoard::from_point(pt, self.size);

        let mut mask = MdMask::new(flip);
        let mut flip_cand = MdMask::new(BitBoard::empty());
        let cnt = cmp::max(cmp::max(pt.0, MAX_SIZE - pt.0 - 1),
                           cmp::max(pt.1, MAX_SIZE - pt.1 - 1));
        for _ in 0..cnt {
            mask = mask.shift(self.offset);
            for (mask, cand) in mask.iter_mut().zip(flip_cand.iter_mut()) {
                if !(*mask & you).is_empty() {
                    *cand |= *mask;
                } else {
                    if !(*mask & me).is_empty() {
                        flip |= *cand;
                    }
                    *mask = BitBoard::empty();
                }
            }
        }

        Some((turn, flip))
    }

    fn compute_move_cand(&self) -> BitBoard {
        let (me, you) = match self.turn {
            Some(Side::Black) => (self.black_cells, self.white_cells),
            Some(Side::White) => (self.white_cells, self.black_cells),
            None => return BitBoard::empty(),
        };

        let mut cand = BitBoard::empty();

        // Search: E Y Y Y M
        let empty = MdMask::new(!me & !you);

        let mut you_mask = MdMask::new(you);
        let mut me_mask = MdMask::new(me).shift(self.offset);
        let mut you_cont_mask = empty;

        for _ in 0..(MAX_SIZE - 2) {
            you_mask = you_mask.shift(self.offset);
            me_mask = me_mask.shift(self.offset);
            you_cont_mask = you_cont_mask & you_mask;

            cand |= (you_cont_mask & me_mask).or_all();
        }

        cand
    }
}
<|endoftext|>


<commit_before>use std::error::Error;
use std::fmt;

use ansi_term::{Color, Style};

/// Holds information about how to style a module
pub struct ModuleStyle {
    pub background: Color,
    pub foreground: Color,
    /// Attributes like bold, italicized, underlined, etc...
    pub text_properties: Vec<Style>,
}

/// Attempts to convert a string into an `ansi_term::Color`.
///
/// Returns a `FromStringError` if the provided string doesn't match
/// any of the colors defined in crate `ansi_term`.
pub fn try_color_from_str(s: &str) -> Result<Color, FromStringError> {
    match s {
        "black" => Ok(Color::Black),
        "red" => Ok(Color::Red),
        "green" => Ok(Color::Green),
        "yellow" => Ok(Color::Yellow),
        "blue" => Ok(Color::Blue),
        "purple" => Ok(Color::Purple),
        "cyan" => Ok(Color::Cyan),
        "white" => Ok(Color::White),
        _ => Err(FromStringError),
    }
}

#[derive(Debug, PartialEq)]
/// Error type for when converting a string fails
pub struct FromStringError;

impl fmt::Display for FromStringError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl Error for FromStringError {
    fn description(&self) -> &str {
        "unrecognized input"
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_color_try_from_str() {
        assert_eq!(try_color_from_str("blue"), Ok(Color::Blue));
        assert_eq!(try_color_from_str("teal"), Err(FromStringError));
    }
}
<commit_msg>refactor: rename FromStringError to TryFromError<commit_after>use std::error::Error;
use std::fmt;

use ansi_term::{Color, Style};

/// Holds information about how to style a module
pub struct ModuleStyle {
    pub background: Color,
    pub foreground: Color,
    /// Attributes like bold, italicized, underlined, etc...
    pub text_properties: Vec<Style>,
}

/// Attempts to convert a string into an `ansi_term::Color`.
///
/// Returns a `TryFromError` if the provided string doesn't match
/// any of the colors defined in crate `ansi_term`.
pub fn try_color_from_str(s: &str) -> Result<Color, TryFromError> {
    match s {
        "black" => Ok(Color::Black),
        "red" => Ok(Color::Red),
        "green" => Ok(Color::Green),
        "yellow" => Ok(Color::Yellow),
        "blue" => Ok(Color::Blue),
        "purple" => Ok(Color::Purple),
        "cyan" => Ok(Color::Cyan),
        "white" => Ok(Color::White),
        _ => Err(TryFromError),
    }
}

#[derive(Debug, PartialEq)]
/// Error type for when converting a string fails
pub struct TryFromError;

impl fmt::Display for TryFromError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl Error for TryFromError {
    fn description(&self) -> &str {
        "unrecognized input"
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_color_try_from_str() {
        assert_eq!(try_color_from_str("blue"), Ok(Color::Blue));
        assert_eq!(try_color_from_str("teal"), Err(TryFromError));
    }
}
<|endoftext|>


<commit_before>use prelude::*;
use opt::{
  ClassOptStats,
};
use rw::{ReadBuffer, WriteBuffer, AccumulateBuffer};

use densearray::{Reshape, ReshapeMut};
use rng::xorshift::{Xorshiftplus128Rng};

use rand::{Rng};
use std::cell::{RefCell};
use std::cmp::{min};
use std::marker::{PhantomData};
use std::rc::{Rc};

#[derive(Clone, Copy, Debug)]
pub struct SgdConfig {
  pub batch_sz:     usize,
  pub minibatch_sz: usize,
  pub step_size:    StepSize,
  pub momentum:     Option<GradientMomentum>,
  //pub l2_reg:       Option<f32>,
}

pub struct SgdWorker<S, Op> where Op: DiffLoss<S, IoBuf=[f32]> {
  cfg:          SgdConfig,
  iter_counter: usize,
  operator:     Rc<RefCell<Op>>,
  cache:        Vec<S>,
  grad_sz:      usize,
  param:        Vec<f32>,
  param_saved:  Vec<f32>,
  grad:         Vec<f32>,
  grad_acc:     Vec<f32>,
  stats_it:     usize,
  stats:        ClassOptStats,
  //_marker:      PhantomData<R>,
}

impl<S, Op> SgdWorker<S, Op> where Op: DiffLoss<S, IoBuf=[f32]> {
  pub fn new(cfg: SgdConfig, operator: Rc<RefCell<Op>>) -> SgdWorker<S, Op> {
    let grad_sz = operator.borrow_mut().diff_param_sz();
    let cache = Vec::with_capacity(cfg.minibatch_sz);
    let mut param = Vec::with_capacity(grad_sz);
    param.resize(grad_sz, 0.0);
    let mut param_saved = Vec::with_capacity(grad_sz);
    param_saved.resize(grad_sz, 0.0);
    let mut grad = Vec::with_capacity(grad_sz);
    grad.resize(grad_sz, 0.0);
    let mut grad_acc = Vec::with_capacity(grad_sz);
    grad_acc.resize(grad_sz, 0.0);
    SgdWorker{
      cfg:          cfg,
      iter_counter: 0,
      operator:     operator,
      cache:        cache,
      grad_sz:      grad_sz,
      param:        param,
      param_saved:  param_saved,
      grad:         grad,
      grad_acc:     grad_acc,
      stats_it:     0,
      stats:        Default::default(),
      //_marker:      PhantomData,
    }
  }
}

impl<S, Op> OptWorker<f32, S> for SgdWorker<S, Op> where Op: DiffLoss<S, IoBuf=[f32]> {
  type Rng = Xorshiftplus128Rng;

  fn init_param(&mut self, rng: &mut Xorshiftplus128Rng) {
    self.operator.borrow_mut().init_param(rng);
    self.operator.borrow_mut().store_diff_param(&mut self.param_saved);
  }

  fn load_local_param(&mut self, param_reader: &mut ReadBuffer<f32>) {
    unimplemented!();
  }

  fn store_local_param(&mut self, param_writer: &mut WriteBuffer<f32>) {
    unimplemented!();
  }

  fn store_global_param(&mut self, param_writer: &mut WriteBuffer<f32>) {
    unimplemented!();
  }

  fn step(&mut self, samples: &mut Iterator<Item=S>) {
    let num_batches = (self.cfg.minibatch_sz + self.cfg.batch_sz - 1) / self.cfg.batch_sz;

    let step_size = match self.cfg.step_size {
      StepSize::Constant(alpha) => {
        alpha
      }
      StepSize::Decay{init_step, step_decay, decay_iters} => {
        let num_decays = self.iter_counter / decay_iters;
        init_step * step_decay.powi(num_decays as i32)
      }
      _ => unimplemented!(),
    };

    self.cache.clear();
    for mut sample in samples.take(self.cfg.minibatch_sz) {
      //sample.mix_weight(1.0 / self.cfg.minibatch_sz as f32);
      self.cache.push(sample);
    }

    let mut operator = self.operator.borrow_mut();

    operator.save_rng_state();
    operator.reset_loss();
    operator.reset_grad();
    if let Some(GradientMomentum::Nesterov(mu)) = self.cfg.momentum {
      //operator.update_diff_param(mu, 1.0, &mut self.grad_acc);
      operator.store_diff_param(&mut self.param);
      self.param.reshape_mut(self.grad_sz).vector_add(mu, self.grad_acc.reshape(self.grad_sz));
      operator.load_diff_param(&mut self.param);
    }
    operator.next_iteration();
    for batch in 0 .. num_batches {
      let batch_start = batch * self.cfg.batch_sz;
      let batch_end = min((batch+1) * self.cfg.batch_sz, self.cfg.minibatch_sz);
      operator.load_batch(&self.cache[batch_start .. batch_end]);
      operator.forward(OpPhase::Learning);
      operator.backward();
    }
    if let Some(GradientMomentum::Nesterov(_)) = self.cfg.momentum {
      operator.load_diff_param(&mut self.param_saved);
    }

    operator.update_nondiff_param(self.iter_counter);

    operator.store_grad(&mut self.grad);
    self.grad.reshape_mut(self.grad_sz).vector_scale(1.0 / self.cfg.minibatch_sz as f32);
    let loss = operator.store_loss() / self.cfg.minibatch_sz as f32;

    if let Some(GradientMomentum::HeavyBall(mu)) = self.cfg.momentum {
      self.grad_acc.reshape_mut(self.grad_sz).vector_scale(mu);
    } else if let Some(GradientMomentum::Nesterov(mu)) = self.cfg.momentum {
      self.grad_acc.reshape_mut(self.grad_sz).vector_scale(mu);
    } else {
      self.grad_acc.reshape_mut(self.grad_sz).set_constant(0.0);
    }
    self.grad_acc.reshape_mut(self.grad_sz).vector_add(-step_size, self.grad.reshape(self.grad_sz));

    //operator.update_diff_param(1.0, 1.0, &mut self.grad_acc);
    operator.store_diff_param(&mut self.param);
    self.param.reshape_mut(self.grad_sz).vector_add(1.0, self.grad_acc.reshape(self.grad_sz));
    operator.load_diff_param(&mut self.param);
    operator.store_diff_param(&mut self.param_saved);

    self.iter_counter += 1;

    self.stats_it += 1;
    self.stats.sample_count += self.cfg.minibatch_sz;
    self.stats.correct_count += operator._store_accuracy();
    self.stats.avg_loss += 1.0 / (self.stats_it as f32) * (loss - self.stats.avg_loss);
  }

  fn eval(&mut self, epoch_sz: usize, samples: &mut Iterator<Item=S>) {
    let mut operator = self.operator.borrow_mut();
    self.cache.clear();
    operator.reset_loss();
    for mut sample in samples.take(epoch_sz) {
      //sample.mix_weight(1.0 / epoch_sz as f32);
      self.cache.push(sample);
      if self.cache.len() == self.cfg.batch_sz {
        operator.load_batch(&self.cache);
        operator.forward(OpPhase::Inference);
        self.cache.clear();
      }
    }
    if self.cache.len() > 0 {
      operator.load_batch(&self.cache);
      operator.forward(OpPhase::Inference);
    }
    let loss = operator.store_loss() / epoch_sz as f32;
    self.stats_it += 1;
    self.stats.sample_count += epoch_sz;
    self.stats.correct_count += operator._store_accuracy();
    self.stats.avg_loss += 1.0 / (self.stats_it as f32) * (loss - self.stats.avg_loss);
  }
}

impl<S, Op> OptStats<ClassOptStats> for SgdWorker<S, Op> where Op: DiffLoss<S, IoBuf=[f32]> {
  fn reset_opt_stats(&mut self) {
    self.stats_it = 0;
    self.stats.sample_count = 0;
    self.stats.correct_count = 0;
    self.stats.avg_loss = 0.0;
  }

  fn get_opt_stats(&self) -> &ClassOptStats {
    &self.stats
  }
}
<commit_msg>Use the new more user friendly vector method names.<commit_after>use prelude::*;
use opt::{
  ClassOptStats,
};
use rw::{ReadBuffer, WriteBuffer, AccumulateBuffer};

use densearray::{Reshape, ReshapeMut};
use rng::xorshift::{Xorshiftplus128Rng};

use rand::{Rng};
use std::cell::{RefCell};
use std::cmp::{min};
use std::marker::{PhantomData};
use std::rc::{Rc};

#[derive(Clone, Copy, Debug)]
pub struct SgdConfig {
  pub batch_sz:     usize,
  pub minibatch_sz: usize,
  pub step_size:    StepSize,
  pub momentum:     Option<GradientMomentum>,
  //pub l2_reg:       Option<f32>,
}

pub struct SgdWorker<S, Loss> where Loss: DiffLoss<S, IoBuf=[f32]> {
  cfg:          SgdConfig,
  iter_counter: usize,
  operator:     Rc<RefCell<Loss>>,
  cache:        Vec<S>,
  grad_sz:      usize,
  param:        Vec<f32>,
  param_saved:  Vec<f32>,
  grad:         Vec<f32>,
  grad_acc:     Vec<f32>,
  stats_it:     usize,
  stats:        ClassOptStats,
  //_marker:      PhantomData<R>,
}

impl<S, Loss> SgdWorker<S, Loss> where Loss: DiffLoss<S, IoBuf=[f32]> {
  pub fn new(cfg: SgdConfig, operator: Rc<RefCell<Loss>>) -> SgdWorker<S, Loss> {
    let grad_sz = operator.borrow_mut().diff_param_sz();
    let cache = Vec::with_capacity(cfg.minibatch_sz);
    let mut param = Vec::with_capacity(grad_sz);
    param.resize(grad_sz, 0.0);
    let mut param_saved = Vec::with_capacity(grad_sz);
    param_saved.resize(grad_sz, 0.0);
    let mut grad = Vec::with_capacity(grad_sz);
    grad.resize(grad_sz, 0.0);
    let mut grad_acc = Vec::with_capacity(grad_sz);
    grad_acc.resize(grad_sz, 0.0);
    SgdWorker{
      cfg:          cfg,
      iter_counter: 0,
      operator:     operator,
      cache:        cache,
      grad_sz:      grad_sz,
      param:        param,
      param_saved:  param_saved,
      grad:         grad,
      grad_acc:     grad_acc,
      stats_it:     0,
      stats:        Default::default(),
      //_marker:      PhantomData,
    }
  }
}

impl<S, Loss> OptWorker<f32, S> for SgdWorker<S, Loss> where Loss: DiffLoss<S, IoBuf=[f32]> {
  type Rng = Xorshiftplus128Rng;

  fn init_param(&mut self, rng: &mut Xorshiftplus128Rng) {
    self.operator.borrow_mut().init_param(rng);
    self.operator.borrow_mut().store_diff_param(&mut self.param_saved);
  }

  fn load_local_param(&mut self, param_reader: &mut ReadBuffer<f32>) {
    unimplemented!();
  }

  fn store_local_param(&mut self, param_writer: &mut WriteBuffer<f32>) {
    unimplemented!();
  }

  fn store_global_param(&mut self, param_writer: &mut WriteBuffer<f32>) {
    unimplemented!();
  }

  fn step(&mut self, samples: &mut Iterator<Item=S>) {
    let num_batches = (self.cfg.minibatch_sz + self.cfg.batch_sz - 1) / self.cfg.batch_sz;

    let step_size = match self.cfg.step_size {
      StepSize::Constant(alpha) => {
        alpha
      }
      StepSize::Decay{init_step, step_decay, decay_iters} => {
        let num_decays = self.iter_counter / decay_iters;
        init_step * step_decay.powi(num_decays as i32)
      }
      _ => unimplemented!(),
    };

    self.cache.clear();
    for mut sample in samples.take(self.cfg.minibatch_sz) {
      //sample.mix_weight(1.0 / self.cfg.minibatch_sz as f32);
      self.cache.push(sample);
    }

    let mut operator = self.operator.borrow_mut();

    operator.save_rng_state();
    operator.reset_loss();
    operator.reset_grad();
    if let Some(GradientMomentum::Nesterov(mu)) = self.cfg.momentum {
      //operator.update_diff_param(mu, 1.0, &mut self.grad_acc);
      operator.store_diff_param(&mut self.param);
      self.param.reshape_mut(self.grad_sz).add(mu, self.grad_acc.reshape(self.grad_sz));
      operator.load_diff_param(&mut self.param);
    }
    operator.next_iteration();
    for batch in 0 .. num_batches {
      let batch_start = batch * self.cfg.batch_sz;
      let batch_end = min((batch+1) * self.cfg.batch_sz, self.cfg.minibatch_sz);
      operator.load_batch(&self.cache[batch_start .. batch_end]);
      operator.forward(OpPhase::Learning);
      operator.backward();
    }
    if let Some(GradientMomentum::Nesterov(_)) = self.cfg.momentum {
      operator.load_diff_param(&mut self.param_saved);
    }

    operator.update_nondiff_param(self.iter_counter);

    operator.store_grad(&mut self.grad);
    self.grad.reshape_mut(self.grad_sz).scale(1.0 / self.cfg.minibatch_sz as f32);
    let loss = operator.store_loss() / self.cfg.minibatch_sz as f32;

    if let Some(GradientMomentum::HeavyBall(mu)) = self.cfg.momentum {
      self.grad_acc.reshape_mut(self.grad_sz).scale(mu);
    } else if let Some(GradientMomentum::Nesterov(mu)) = self.cfg.momentum {
      self.grad_acc.reshape_mut(self.grad_sz).scale(mu);
    } else {
      self.grad_acc.reshape_mut(self.grad_sz).set_constant(0.0);
    }
    self.grad_acc.reshape_mut(self.grad_sz).add(-step_size, self.grad.reshape(self.grad_sz));

    //operator.update_diff_param(1.0, 1.0, &mut self.grad_acc);
    operator.store_diff_param(&mut self.param);
    self.param.reshape_mut(self.grad_sz).add(1.0, self.grad_acc.reshape(self.grad_sz));
    operator.load_diff_param(&mut self.param);
    operator.store_diff_param(&mut self.param_saved);

    self.iter_counter += 1;

    self.stats_it += 1;
    self.stats.sample_count += self.cfg.minibatch_sz;
    self.stats.correct_count += operator._store_accuracy();
    self.stats.avg_loss += 1.0 / (self.stats_it as f32) * (loss - self.stats.avg_loss);
  }

  fn eval(&mut self, epoch_sz: usize, samples: &mut Iterator<Item=S>) {
    let mut operator = self.operator.borrow_mut();
    self.cache.clear();
    operator.reset_loss();
    for mut sample in samples.take(epoch_sz) {
      //sample.mix_weight(1.0 / epoch_sz as f32);
      self.cache.push(sample);
      if self.cache.len() == self.cfg.batch_sz {
        operator.load_batch(&self.cache);
        operator.forward(OpPhase::Inference);
        self.cache.clear();
      }
    }
    if self.cache.len() > 0 {
      operator.load_batch(&self.cache);
      operator.forward(OpPhase::Inference);
    }
    let loss = operator.store_loss() / epoch_sz as f32;
    self.stats_it += 1;
    self.stats.sample_count += epoch_sz;
    self.stats.correct_count += operator._store_accuracy();
    self.stats.avg_loss += 1.0 / (self.stats_it as f32) * (loss - self.stats.avg_loss);
  }
}

impl<S, Loss> OptStats<ClassOptStats> for SgdWorker<S, Loss> where Loss: DiffLoss<S, IoBuf=[f32]> {
  fn reset_opt_stats(&mut self) {
    self.stats_it = 0;
    self.stats.sample_count = 0;
    self.stats.correct_count = 0;
    self.stats.avg_loss = 0.0;
  }

  fn get_opt_stats(&self) -> &ClassOptStats {
    &self.stats
  }
}
<|endoftext|>


<commit_before>use std::path::PathBuf;
use std::cmp::Ordering;

#[derive(Debug, PartialEq)]
pub struct Result {
    pub path: PathBuf,
    score: f32,
}

/// Given a set of paths, `find` returns a set of `Result` objects
/// ordered by increasing score values (first values are closest matches).
/// If the result set is larger than `max_results`, the set is reduced to
/// that size.
///
/// # Examples
///
/// ```rust
/// use std::path::PathBuf;
/// use bloodhound::matching::find;
///
/// let paths = vec![PathBuf::from("bloodhound.rs"), PathBuf::from("lib.rs")];
/// let matches = find("lib", &paths, 1);
///
/// assert_eq!(matches[0].path.to_str().unwrap(), "lib.rs");
/// ```
pub fn find(needle: &str, haystack: &Vec<PathBuf>, max_results: usize) -> Vec<Result> {
    let mut results = Vec::new();

    // Calculate a score for each of the haystack entries.
    for path in haystack.iter() {
        match path.to_str() {
            Some(path_string) => {
                results.push(Result{
                    path: path.clone(),
                    score: similarity(needle, path_string)
                });
            },
            None => (),
        }
    }

    // Sort the results in ascending order (higher values are worse).
    results.sort_by(|a, b| {
        if a.score > b.score {
            Ordering::Less
        } else if a.score < b.score {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    });

    // Make sure we don't exceed the specified result limit.
    results.truncate(max_results);

    results
}

/// Compares the query string to the subject,
/// and returns a score between 0 and 1.
fn similarity(query: &str, subject: &str) -> f32 {
    // Exact matches produce a perfect score.
    if query == subject {
        return 1.0;
    }

    let mut overall_score = 0.0;
    let subject_length = subject.chars().count();
    for query_char in query.chars() {
        let mut character_score = 0.0;
        for subject_char in subject.chars() {
            // For every occurrence of a query character in the
            // subject increase the individual character's score.
            if query_char == subject_char {
                character_score += 1.0;
            }
        }

        // Limit the character score to a maximum value
        // of "1" and add it to the overall score.
        character_score /= subject_length as f32;
        overall_score += character_score;
    }

    // Return an overall score, limited to a maximum value of "1".
    overall_score / subject.chars().count() as f32
}

#[cfg(test)]
mod tests {
    use super::find;
    use super::similarity;
    use std::path::PathBuf;

    #[test]
    fn find_returns_a_correctly_ordered_set_of_results() {
        let haystack = vec![PathBuf::from("src/hound.rs"),
            PathBuf::from("lib/hounds.rs"), PathBuf::from("Houndfile")];
        let expected_results = vec![PathBuf::from("Houndfile"), PathBuf::from("src/hound.rs")];
        let results = find("Hound", &haystack, 2);
        for i in 0..2 {
            assert_eq!(results[i].path, expected_results[i]);
        }
    }

    #[test]
    fn find_returns_a_correctly_limited_set_of_results() {
        let haystack = vec![PathBuf::from("src/hound.rs"),
            PathBuf::from("lib/hounds.rs"), PathBuf::from("Houndfile")];
        let results = find("Hound", &haystack, 2);
        assert_eq!(results.len(), 2);
    }

    #[test]
    fn similarity_correctly_scores_perfect_matches() {
        assert_eq!(similarity("src/hound.rs", "src/hound.rs"), 1.0);
    }

    #[test]
    fn similarity_correctly_scores_completely_different_terms() {
        assert_eq!(similarity("lib", "src"), 0.0);
    }

    #[test]
    fn similarity_scores_based_on_term_length() {
        let differing_length_score = similarity("houn", "houndhound");

        // Don't use a perfect match, since those product a perfect score.
        let same_length_score = similarity("houn", "hound");
        assert!(same_length_score > differing_length_score);
    }
}
<commit_msg>Increase score for consecutive character matches.<commit_after>use std::path::PathBuf;
use std::cmp::Ordering;

#[derive(Debug, PartialEq)]
pub struct Result {
    pub path: PathBuf,
    score: f32,
}

/// Given a set of paths, `find` returns a set of `Result` objects
/// ordered by increasing score values (first values are closest matches).
/// If the result set is larger than `max_results`, the set is reduced to
/// that size.
///
/// # Examples
///
/// ```rust
/// use std::path::PathBuf;
/// use bloodhound::matching::find;
///
/// let paths = vec![PathBuf::from("bloodhound.rs"), PathBuf::from("lib.rs")];
/// let matches = find("lib", &paths, 1);
///
/// assert_eq!(matches[0].path.to_str().unwrap(), "lib.rs");
/// ```
pub fn find(needle: &str, haystack: &Vec<PathBuf>, max_results: usize) -> Vec<Result> {
    let mut results = Vec::new();

    // Calculate a score for each of the haystack entries.
    for path in haystack.iter() {
        match path.to_str() {
            Some(path_string) => {
                results.push(Result{
                    path: path.clone(),
                    score: similarity(needle, path_string)
                });
            },
            None => (),
        }
    }

    // Sort the results in ascending order (higher values are worse).
    results.sort_by(|a, b| {
        if a.score > b.score {
            Ordering::Less
        } else if a.score < b.score {
            Ordering::Greater
        } else {
            Ordering::Equal
        }
    });

    // Make sure we don't exceed the specified result limit.
    results.truncate(max_results);

    results
}

/// Compares the query string to the subject,
/// and returns a score between 0 and 1.
fn similarity(query: &str, subject: &str) -> f32 {
    // Exact matches produce a perfect score.
    if query == subject {
        return 1.0;
    }

    let mut overall_score = 0.0;
    let subject_length = subject.chars().count();

    // We'll use these to add weight to consecutive character matches.
    let mut previous_match_indices = Vec::new();
    let mut current_match_indices  = Vec::new();

    for query_char in query.chars() {
        let mut character_score = 0.0;
        for (index, subject_char) in subject.chars().enumerate() {
            // For every occurrence of a query character in the
            // subject increase the individual character's score.
            if query_char == subject_char {
                character_score += 1.0;

                // Track the index at which we found a match,
                // so that we can detect subsequent matches.
                current_match_indices.push(index);

                // If the last query character matched the previous subject
                // character, there are at least two consecutive characters
                // that match; bump the character score to account for that.
                if index > 0 && previous_match_indices.contains(&(index-1)) {
                    character_score += 1.0;
                }
            }
        }

        // Limit the character score to a maximum value
        // of "1" and add it to the overall score.
        character_score /= subject_length as f32;
        overall_score += character_score;

        // The current matches become the
        // previous ones for the next iteration.
        previous_match_indices = current_match_indices;
        current_match_indices = Vec::new();
    }

    // Return an overall score, limited to a maximum value of "1".
    overall_score / subject.chars().count() as f32
}

#[cfg(test)]
mod tests {
    use super::find;
    use super::similarity;
    use std::path::PathBuf;

    #[test]
    fn find_returns_a_correctly_ordered_set_of_results() {
        let haystack = vec![PathBuf::from("src/hound.rs"),
            PathBuf::from("lib/hounds.rs"), PathBuf::from("Houndfile")];
        let expected_results = vec![PathBuf::from("Houndfile"), PathBuf::from("src/hound.rs")];
        let results = find("Hound", &haystack, 2);
        for i in 0..2 {
            assert_eq!(results[i].path, expected_results[i]);
        }
    }

    #[test]
    fn find_returns_a_correctly_limited_set_of_results() {
        let haystack = vec![PathBuf::from("src/hound.rs"),
            PathBuf::from("lib/hounds.rs"), PathBuf::from("Houndfile")];
        let results = find("Hound", &haystack, 2);
        assert_eq!(results.len(), 2);
    }

    #[test]
    fn similarity_correctly_scores_perfect_matches() {
        assert_eq!(similarity("src/hound.rs", "src/hound.rs"), 1.0);
    }

    #[test]
    fn similarity_correctly_scores_completely_different_terms() {
        assert_eq!(similarity("lib", "src"), 0.0);
    }

    #[test]
    fn similarity_scores_based_on_term_length() {
        let differing_length_score = similarity("houn", "houndhound");

        // Don't use a perfect match, since those product a perfect score.
        let same_length_score = similarity("houn", "hound");
        assert!(same_length_score > differing_length_score);
    }

    #[test]
    fn similarity_score_increases_for_consecutive_matches() {
        // Don't use a perfect match, since those product a perfect score.
        let properly_ordered_score = similarity("houn", "hound");

        let improperly_ordered_score = similarity("nuoh", "hound");
        assert!(properly_ordered_score > improperly_ordered_score);
    }
}
<|endoftext|>


<commit_before>// Zinc, the bare metal stack for rust.
// Copyright 2014 Ben Gamari <bgamari@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Condition variables

pub use os::cond_var::internal::{CondVar, COND_VAR_INIT};

#[cfg(multitasking)]
mod internal {
  use core::option::{None, Some};
  use core::ty::Unsafe;
  use core::marker;
  use core::marker::Sync;

  use hal::cortex_m3::sched::NoInterrupts;
  use util::queue::{Queue, Node};
  use os::task::{TaskDescriptor, Tasks};

  /// A condition variable
  pub struct CondVar {
    waiting: Queue<*mut TaskDescriptor>
  }

  /// Static initializer
  pub const COND_VAR_INIT: CondVar = CondVar {
    waiting: Queue {
      head: Unsafe { value: 0 as *mut Node<*mut TaskDescriptor>, marker1: marker::InvariantType },
      tail: Unsafe { value: 0 as *mut Node<*mut TaskDescriptor>, marker1: marker::InvariantType },
    }
  };

  impl CondVar {
    /// Create a new condition variable
    pub fn new() -> CondVar {
      CondVar { waiting: Queue::new() }
    }

    /// Wait on a condition variable.
    pub fn wait(&self) {
      /*
       * The signalling thread is responsible for removing the waiting
       * thread which ensures that a signal wakes up exactly one thread
       * whenever there is one waiting.
       */
      unsafe {
        let crit = NoInterrupts::new();
        let mut waiting = Node::new(Tasks.current_task() as *mut TaskDescriptor);
        self.waiting.push(&mut waiting, &crit);
        Tasks.current_task().block(crit);
      }
    }

    /// Wake up a thread waiting on a condition variable.
    pub fn signal(&self) {
      unsafe {
        let crit = NoInterrupts::new();
        match self.waiting.pop(&crit) {
          None => { },
          Some(task) => (*(*task).data).unblock(&crit)
        }
      }
    }

    /// Wake up all threads waiting on a condition variable.
    pub fn broadcast(&self) {
      unsafe {
        let crit = NoInterrupts::new();
        loop {
          match self.waiting.pop(&crit) {
            None => break,
            Some(task) => (*(*task).data).unblock(&crit)
          }
        }
      }
    }
  }

  impl Sync for CondVar {}
}

#[cfg(not(multitasking))]
mod internal {
  use core::marker;
  use core::marker::Sync;
  use core::cell::UnsafeCell;

  use util::support::wfi;

  /// A condition variable
  pub struct CondVar {
    waiting: UnsafeCell<bool>,
    nocopy: marker::NoCopy
  }

  /// Static initializer
  pub const COND_VAR_INIT: CondVar = CondVar {
    waiting: UnsafeCell { value: false },
    nocopy: marker::NoCopy,
  };

  impl CondVar {
    /// Create a new condition variable
    pub fn new() -> CondVar {
      CondVar {
        waiting: UnsafeCell::new(false),
        nocopy: marker::NoCopy,
      }
    }

    /// Wait on a condition variable.
    pub fn wait(&self) {
      unsafe {
        // TODO(bgamari): There is a race condition here
        *self.waiting.get() = true;
        while *self.waiting.get() {
          wfi();
        }
      }
    }

    /// Wake up a thread waiting on a condition variable.
    pub fn signal(&self) {
      unsafe {
        *self.waiting.get() = false;
      }
    }

    /// Wake up all threads waiting on a condition variable.
    pub fn broadcast(&self) {
      self.signal();
    }
  }

  unsafe impl Sync for CondVar {}
}
<commit_msg>Updated for removal of NoCopy marker in nightly<commit_after>// Zinc, the bare metal stack for rust.
// Copyright 2014 Ben Gamari <bgamari@gmail.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Condition variables

pub use os::cond_var::internal::{CondVar, COND_VAR_INIT};

#[cfg(multitasking)]
mod internal {
  use core::option::{None, Some};
  use core::ty::Unsafe;
  use core::marker::Sync;

  use hal::cortex_m3::sched::NoInterrupts;
  use util::queue::{Queue, Node};
  use os::task::{TaskDescriptor, Tasks};

  /// A condition variable
  pub struct CondVar {
    waiting: Queue<*mut TaskDescriptor>
  }

  /// Static initializer
  pub const COND_VAR_INIT: CondVar = CondVar {
    waiting: Queue {
      head: Unsafe { value: 0 as *mut Node<*mut TaskDescriptor>, marker1: marker::InvariantType },
      tail: Unsafe { value: 0 as *mut Node<*mut TaskDescriptor>, marker1: marker::InvariantType },
    }
  };

  impl CondVar {
    /// Create a new condition variable
    pub fn new() -> CondVar {
      CondVar { waiting: Queue::new() }
    }

    /// Wait on a condition variable.
    pub fn wait(&self) {
      /*
       * The signalling thread is responsible for removing the waiting
       * thread which ensures that a signal wakes up exactly one thread
       * whenever there is one waiting.
       */
      unsafe {
        let crit = NoInterrupts::new();
        let mut waiting = Node::new(Tasks.current_task() as *mut TaskDescriptor);
        self.waiting.push(&mut waiting, &crit);
        Tasks.current_task().block(crit);
      }
    }

    /// Wake up a thread waiting on a condition variable.
    pub fn signal(&self) {
      unsafe {
        let crit = NoInterrupts::new();
        match self.waiting.pop(&crit) {
          None => { },
          Some(task) => (*(*task).data).unblock(&crit)
        }
      }
    }

    /// Wake up all threads waiting on a condition variable.
    pub fn broadcast(&self) {
      unsafe {
        let crit = NoInterrupts::new();
        loop {
          match self.waiting.pop(&crit) {
            None => break,
            Some(task) => (*(*task).data).unblock(&crit)
          }
        }
      }
    }
  }

  impl Sync for CondVar {}
}

#[cfg(not(multitasking))]
mod internal {
  use core::marker::Sync;
  use core::cell::UnsafeCell;

  use util::support::wfi;

  /// A condition variable
  pub struct CondVar {
    waiting: UnsafeCell<bool>,
  }

  /// Static initializer
  pub const COND_VAR_INIT: CondVar = CondVar {
    waiting: UnsafeCell::new(false),
  };

  impl CondVar {
    /// Create a new condition variable
    pub fn new() -> CondVar {
      CondVar {
        waiting: UnsafeCell::new(false),
      }
    }

    /// Wait on a condition variable.
    pub fn wait(&self) {
      unsafe {
        // TODO(bgamari): There is a race condition here
        *self.waiting.get() = true;
        while *self.waiting.get() {
          wfi();
        }
      }
    }

    /// Wake up a thread waiting on a condition variable.
    pub fn signal(&self) {
      unsafe {
        *self.waiting.get() = false;
      }
    }

    /// Wake up all threads waiting on a condition variable.
    pub fn broadcast(&self) {
      self.signal();
    }
  }

  unsafe impl Sync for CondVar {}
}
<|endoftext|>


<commit_before>/// The definitions for every function used in prelude
/// "The built in functions"
/// These special functions allow for things like defining operators,
/// performing basic operations and special math functions

use math::rich_number::RichNumber;

use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};
use eval::built_in_function::BuiltInFunction;

pub fn setup_prelude(context: &mut EvalContext) {
    define_math(context);
}

fn define_math(context: &mut EvalContext) {
    define_numeric_binary_op(context, "pow", |a, b| a.pow(b), false);
    define_numeric_binary_op(context, "mul", |a, b| a * b, false);

    define_numeric_binary_op(context, "add", |a, b| a + b, true);
    define_numeric_binary_op(context, "sub", |a, b| a - b, true);
    define_numeric_binary_op(context, "div", |a, b| a / b, true);
    define_numeric_binary_op(context, "mod", |a, b| a % b, true);

    define_boolean_binary_op(context, "eq", |a, b| a == b, true);
    define_boolean_binary_op(context, "ne", |a, b| a != b, true);

    //TODO: ge, le, gt, lt, neg, not, sin, cos, tan, etc.
}

fn define_boolean_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(RichNumber, RichNumber) -> bool {

    define_binary_op(context, name,
        move |a, b| ContextItem::Boolean(operator(a, b)), coerce_first);
}

fn define_numeric_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(RichNumber, RichNumber) -> RichNumber {

    define_binary_op(context, name,
        move |a, b| ContextItem::Number(operator(a, b)), coerce_first);
}

fn define_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(RichNumber, RichNumber) -> ContextItem {

    const PARAMS_LENGTH: usize = 2;
    define_built_in(context, name, PARAMS_LENGTH,
        BuiltInFunction::new(move |context, mut params| {
            if params.len() != PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(PARAMS_LENGTH))
            }

            if !params[0].is_number() || !params[1].is_number() {
                return Err(EvalError::InvalidParams("Expected two numbers".to_owned()));
            }

            let mut rhs = params.pop().unwrap().unwrap_number();
            let lhs = params.pop().unwrap().unwrap_number();

            if coerce_first {
                // May possibly attempt to convert in the other
                // direction too in the future
                rhs = try!(context.convert(rhs, lhs.unit)).unwrap_number();
            }

            Ok(operator(lhs, rhs))
        })
    );
}

fn define_built_in(
    context: &mut EvalContext,
    name: &str,
    params: usize,
    function: BuiltInFunction,
) {
    let item = ContextItem::built_in_defaults(function, params);
    context.set(name, item);
}

#[cfg(test)]
mod tests {
    use ast::*;
    use eval::eval_context::EvalContext;
    use math::rich_number::RichNumber;

    #[test]
    fn math_operators() {
        let mut context = EvalContext::prelude();

        // add (1 (sub (mul 2 (pow 4 3)) (div (mod 7 3) 10)))
        // 1 + ((2 * (4 ** 3)) - ((7 % 3) / 10)
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Symbol("add".to_owned())),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Number(1f64)),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Symbol("sub".to_owned())),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("mul".to_owned())),
                        ExprItem::SingleTerm(Term::Number(2f64)),
                        ExprItem::Group(vec![
                            ExprItem::SingleTerm(Term::Symbol("pow".to_owned())),
                            ExprItem::SingleTerm(Term::Number(4f64)),
                            ExprItem::SingleTerm(Term::Number(3f64)),
                        ]),
                    ]),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("div".to_owned())),
                        ExprItem::Group(vec![
                            ExprItem::SingleTerm(Term::Symbol("mod".to_owned())),
                            ExprItem::SingleTerm(Term::Number(7f64)),
                            ExprItem::SingleTerm(Term::Number(3f64)),
                        ]),
                        ExprItem::SingleTerm(Term::Number(10f64)),
                    ]),
                ]),
            ]),
        ])).unwrap().unwrap_number(), RichNumber::from(128.9f64));
    }
}
<commit_msg>Removing the extra group to make the prelude test work<commit_after>/// The definitions for every function used in prelude
/// "The built in functions"
/// These special functions allow for things like defining operators,
/// performing basic operations and special math functions

use math::rich_number::RichNumber;

use eval::context_item::ContextItem;
use eval::eval_context::{EvalContext, EvalError};
use eval::built_in_function::BuiltInFunction;

pub fn setup_prelude(context: &mut EvalContext) {
    define_math(context);
}

fn define_math(context: &mut EvalContext) {
    define_numeric_binary_op(context, "pow", |a, b| a.pow(b), false);
    define_numeric_binary_op(context, "mul", |a, b| a * b, false);

    define_numeric_binary_op(context, "add", |a, b| a + b, true);
    define_numeric_binary_op(context, "sub", |a, b| a - b, true);
    define_numeric_binary_op(context, "div", |a, b| a / b, true);
    define_numeric_binary_op(context, "mod", |a, b| a % b, true);

    define_boolean_binary_op(context, "eq", |a, b| a == b, true);
    define_boolean_binary_op(context, "ne", |a, b| a != b, true);

    //TODO: ge, le, gt, lt, neg, not, sin, cos, tan, etc.
}

fn define_boolean_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(RichNumber, RichNumber) -> bool {

    define_binary_op(context, name,
        move |a, b| ContextItem::Boolean(operator(a, b)), coerce_first);
}

fn define_numeric_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(RichNumber, RichNumber) -> RichNumber {

    define_binary_op(context, name,
        move |a, b| ContextItem::Number(operator(a, b)), coerce_first);
}

fn define_binary_op<F: 'static>(context: &mut EvalContext, name: &str, operator: F, coerce_first: bool)
    where F: Fn(RichNumber, RichNumber) -> ContextItem {

    const PARAMS_LENGTH: usize = 2;
    define_built_in(context, name, PARAMS_LENGTH,
        BuiltInFunction::new(move |context, mut params| {
            if params.len() != PARAMS_LENGTH {
                return Err(EvalError::ExpectedParams(PARAMS_LENGTH))
            }

            if !params[0].is_number() || !params[1].is_number() {
                return Err(EvalError::InvalidParams("Expected two numbers".to_owned()));
            }

            let mut rhs = params.pop().unwrap().unwrap_number();
            let lhs = params.pop().unwrap().unwrap_number();

            if coerce_first {
                // May possibly attempt to convert in the other
                // direction too in the future
                rhs = try!(context.convert(rhs, lhs.unit)).unwrap_number();
            }

            Ok(operator(lhs, rhs))
        })
    );
}

fn define_built_in(
    context: &mut EvalContext,
    name: &str,
    params: usize,
    function: BuiltInFunction,
) {
    let item = ContextItem::built_in_defaults(function, params);
    context.set(name, item);
}

#[cfg(test)]
mod tests {
    use ast::*;
    use eval::eval_context::EvalContext;
    use math::rich_number::RichNumber;

    #[test]
    fn math_operators() {
        let mut context = EvalContext::prelude();

        // add 1 (sub (mul 2 (pow 4 3)) (div (mod 7 3) 10))
        // 1 + ((2 * (4 ** 3)) - ((7 % 3) / 10)
        assert_eq!(context.apply(Statement::Expression(vec![
            ExprItem::SingleTerm(Term::Symbol("add".to_owned())),
            ExprItem::SingleTerm(Term::Number(1f64)),
            ExprItem::Group(vec![
                ExprItem::SingleTerm(Term::Symbol("sub".to_owned())),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Symbol("mul".to_owned())),
                    ExprItem::SingleTerm(Term::Number(2f64)),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("pow".to_owned())),
                        ExprItem::SingleTerm(Term::Number(4f64)),
                        ExprItem::SingleTerm(Term::Number(3f64)),
                    ]),
                ]),
                ExprItem::Group(vec![
                    ExprItem::SingleTerm(Term::Symbol("div".to_owned())),
                    ExprItem::Group(vec![
                        ExprItem::SingleTerm(Term::Symbol("mod".to_owned())),
                        ExprItem::SingleTerm(Term::Number(7f64)),
                        ExprItem::SingleTerm(Term::Number(3f64)),
                    ]),
                    ExprItem::SingleTerm(Term::Number(10f64)),
                ]),
            ]),
        ])).unwrap().unwrap_number(), RichNumber::from(128.9f64));
    }
}
<|endoftext|>


<commit_before>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use futures::future;
use futures::future::FutureResult;
use hyper;
use hyper::{Method, StatusCode};
use hyper::server::{Request, Response};

use super::header::ProxyHeader;
use super::tunnel::ProxyTunnelBuilder;
use header::request_shard::HeaderRequestBloomRequestShard;
use header::status::HeaderBloomStatusValue;
use cache::read::CacheRead;
use cache::write::CacheWrite;
use cache::route::CacheRoute;

pub struct ProxyServeBuilder;

pub struct ProxyServe;

pub type ProxyServeFuture = FutureResult<Response, hyper::Error>;

impl ProxyServeBuilder {
    pub fn new() -> ProxyServe {
        ProxyServe {}
    }
}

impl ProxyServe {
    pub fn handle(&self, req: Request) -> ProxyServeFuture {
        info!("handled request: {} on {}", req.method(), req.path());

        let mut res = Response::new();

        if req.headers().has::<HeaderRequestBloomRequestShard>() == true {
            match *req.method() {
                Method::Options
                | Method::Head
                | Method::Get
                | Method::Post
                | Method::Patch
                | Method::Put
                | Method::Delete => {
                    self.accept(&req, &mut res)
                }
                _ => {
                    self.reject(&req, &mut res, StatusCode::MethodNotAllowed)
                }
            }
        } else {
            self.reject(&req, &mut res, StatusCode::NotExtended)
        }

        future::ok(res)
    }

    fn accept(&self, req: &Request, res: &mut Response) {
        self.tunnel(req, res);
    }

    fn reject(&self, req: &Request, res: &mut Response, status: StatusCode) {
        res.set_status(status);

        match *req.method() {
            Method::Get | Method::Post | Method::Patch | Method::Put => {
                res.set_body(format!("{}", status));
            }
            _ => {}
        }
    }

    fn tunnel(&self, req: &Request, res: &mut Response) {
        let (auth, shard) = ProxyHeader::parse_from_request(req.headers());

        let ns = CacheRoute::gen_ns(shard, auth, req.version(), req.method(),
            req.path(), req.query());

        // TODO: support for 304 Not Modified here (return empty content \
        //   to ongoing specific client, but still read/populate cache normally)

        // TODO: implement support for Bloom-Response-Bucket
        // CONCERN: how to link this to the gen_ns() utility? We dont \
        //   know about which route is mapped to which bucket in advance. \
        //   so maybe redesign this part.  <--- FOUND OUT
        // WAIT TO GO: any route can be 'tagged' as 'bucket' using a generic \
        //   tagging system. As buckets are only used for cache expiration, \
        //   and not cache storage, they are only useful as 'tags'. This way \
        //   we dont need to know them in advance.

        info!("tunneling for ns = {}", ns);

        match CacheRead::acquire(ns.as_ref()) {
            Ok(cached_value) => {
                self.dispatch_cached(res, cached_value)
            },
            Err(_) => {
                // TODO -> connect to API using ConfigProxy[:shard].inet
                    // TODO -> enforce timeouts:
                        //   - ConfigProxy.tunnel_connect_timeout
                        //   - ConfigProxy.tunnel_read_timeout
                        //   - ConfigProxy.tunnel_send_timeout
                // TODO -> CacheWrite::save(ns, req, res) (check return value)
                    // TODO -> return == true -> set 'Bloom-Status' as 'MISS'
                    // TODO -> return == false -> set 'Bloom-Status' as 'DIRECT'

                // Acquire response object from client to API

                // TODO: move this to a common factory, ie global.
                // CRITICAL: avoid spawning new threads and destroying them \
                //   for each connection.
                let mut tunnel = ProxyTunnelBuilder::new();

                match tunnel.run(req.method()) {
                    Ok(tunnel_res) => {
                        if CacheWrite::save(ns.as_ref(), req, &tunnel_res) ==
                            true {
                            self.dispatch_direct(res, tunnel_res,
                                HeaderBloomStatusValue::Miss)
                        } else {
                            self.dispatch_direct(res, tunnel_res,
                                HeaderBloomStatusValue::Direct)
                        }
                    }
                    _ => {
                        self.dispatch_failure(res)
                    }
                }
            }
        }

        debug!("done tunneling for ns = {}", ns);
    }

    fn dispatch_cached(&self, res: &mut Response, value: String) {
        // TODO: handle 'tunnel_res: &mut Response' here.

        // TODO: append status
        // TODO: append headers
        res.set_status(StatusCode::Accepted);  // <-- TODO: dynamic status

        // TODO: issue w/ borrow
        // res.with_header(HeaderBloomStatus(HeaderBloomStatusValue::Hit));

        // TODO: parse value and split headers (restore them + set body)

        res.set_body(value);
    }

    fn dispatch_direct(&self, res: &mut Response, tunnel_res: Response,
        bloomStatus: HeaderBloomStatusValue) {
        // TODO: handle 'tunnel_res: &mut Response' here.

        // TODO: append status
        // TODO: append headers
        res.set_status(tunnel_res.status());  // <-- TODO: dynamic status

        // res.set_header(HeaderBloomStatus(bloomStatus))

        res.set_body(tunnel_res.body());
    }

    fn dispatch_failure(&self, res: &mut Response) {
        let status = StatusCode::BadGateway;

        res.set_status(status);

        // TODO: issue w/ borrow
        // res.with_header(HeaderBloomStatus(HeaderBloomStatusValue::Offline));

        res.set_body(format!("{}", status));
    }
}
<commit_msg>Refactor how responses are built<commit_after>// Bloom
//
// HTTP REST API caching middleware
// Copyright: 2017, Valerian Saliou <valerian@valeriansaliou.name>
// License: Mozilla Public License v2.0 (MPL v2.0)

use futures::future;
use futures::future::FutureResult;
use hyper;
use hyper::{Method, StatusCode};
use hyper::server::{Request, Response};

use super::header::ProxyHeader;
use super::tunnel::ProxyTunnelBuilder;
use header::request_shard::HeaderRequestBloomRequestShard;
use header::status::HeaderBloomStatusValue;
use cache::read::CacheRead;
use cache::write::CacheWrite;
use cache::route::CacheRoute;

pub struct ProxyServeBuilder;

pub struct ProxyServe;

pub type ProxyServeFuture = FutureResult<Response, hyper::Error>;

impl ProxyServeBuilder {
    pub fn new() -> ProxyServe {
        ProxyServe {}
    }
}

impl ProxyServe {
    pub fn handle(&self, req: Request) -> ProxyServeFuture {
        info!("handled request: {} on {}", req.method(), req.path());

        let res = if req.headers().has::<HeaderRequestBloomRequestShard>() ==
            true {
            match *req.method() {
                Method::Options
                | Method::Head
                | Method::Get
                | Method::Post
                | Method::Patch
                | Method::Put
                | Method::Delete => {
                    self.accept(&req)
                }
                _ => {
                    self.reject(&req, StatusCode::MethodNotAllowed)
                }
            }
        } else {
            self.reject(&req, StatusCode::NotExtended)
        };

        future::ok(res)
    }

    fn accept(&self, req: &Request) -> Response {
        self.tunnel(req)
    }

    fn reject(&self, req: &Request, status: StatusCode) -> Response {
        Response::new()
            .with_status(status)
            .with_body(
                match *req.method() {
                    Method::Get
                    | Method::Post
                    | Method::Patch
                    | Method::Put => {
                        format!("{}", status)
                    }
                    _ => String::new()
                }
            )
    }

    fn tunnel(&self, req: &Request) -> Response {
        let (auth, shard) = ProxyHeader::parse_from_request(req.headers());

        let ns = CacheRoute::gen_ns(shard, auth, req.version(), req.method(),
            req.path(), req.query());

        // TODO: support for 304 Not Modified here (return empty content \
        //   to ongoing specific client, but still read/populate cache normally)

        // TODO: implement support for Bloom-Response-Bucket
        // CONCERN: how to link this to the gen_ns() utility? We dont \
        //   know about which route is mapped to which bucket in advance. \
        //   so maybe redesign this part.  <--- FOUND OUT
        // WAIT TO GO: any route can be 'tagged' as 'bucket' using a generic \
        //   tagging system. As buckets are only used for cache expiration, \
        //   and not cache storage, they are only useful as 'tags'. This way \
        //   we dont need to know them in advance.

        info!("tunneling for ns = {}", ns);

        match CacheRead::acquire(ns.as_ref()) {
            Ok(cached_value) => {
                self.dispatch_cached(cached_value)
            },
            Err(_) => {
                // TODO -> connect to API using ConfigProxy[:shard].inet
                    // TODO -> enforce timeouts:
                        //   - ConfigProxy.tunnel_connect_timeout
                        //   - ConfigProxy.tunnel_read_timeout
                        //   - ConfigProxy.tunnel_send_timeout
                // TODO -> CacheWrite::save(ns, req, res) (check return value)
                    // TODO -> return == true -> set 'Bloom-Status' as 'MISS'
                    // TODO -> return == false -> set 'Bloom-Status' as 'DIRECT'

                // Acquire response object from client to API

                // TODO: move this to a common factory, ie global.
                // CRITICAL: avoid spawning new threads and destroying them \
                //   for each connection.
                let mut tunnel = ProxyTunnelBuilder::new();

                match tunnel.run(req.method()) {
                    Ok(tunnel_res) => {
                        if CacheWrite::save(ns.as_ref(), req, &tunnel_res) ==
                            true {
                            self.dispatch_direct(tunnel_res,
                                HeaderBloomStatusValue::Miss)
                        } else {
                            self.dispatch_direct(tunnel_res,
                                HeaderBloomStatusValue::Direct)
                        }
                    }
                    _ => {
                        self.dispatch_failure()
                    }
                }
            }
        }
    }

    fn dispatch_cached(&self, value: String) -> Response {
        // TODO: handle 'tunnel_res: &mut Response' here.

        // TODO: issue w/ borrow
        // res.with_header(HeaderBloomStatus(HeaderBloomStatusValue::Hit));

        // TODO: parse value and split headers (restore them + set body)

        // TODO: append status
        // TODO: append headers

        Response::new()
            .with_status(StatusCode::Accepted)  // <-- TODO: dynamic status
            .with_body(value)
    }

    fn dispatch_direct(&self, tunnel_res: Response,
        bloomStatus: HeaderBloomStatusValue) -> Response {
        // TODO: handle 'tunnel_res: &mut Response' here.

        // res.set_header(HeaderBloomStatus(bloomStatus))

        // TODO: append status
        // TODO: append headers

        Response::new()
            .with_status(tunnel_res.status())  // <-- TODO: dynamic status
            .with_body(tunnel_res.body())
    }

    fn dispatch_failure(&self) -> Response {
        let status = StatusCode::BadGateway;

        // TODO: issue w/ borrow
        // res.with_header(HeaderBloomStatus(HeaderBloomStatusValue::Offline));

        Response::new()
            .with_status(status)
            .with_body(format!("{}", status))
    }
}
<|endoftext|>