Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Python 3 support via future package #466

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d0a80f4
Make MOE more Python3 friendly i.e. run futurize -1 -n -w
Sep 2, 2016
ada677f
No need to use absolute_import in setup.py
Sep 2, 2016
a322921
Make MOE both Python 2 and 3 compatible using futurize second stage.
Sep 2, 2016
1588725
Add future dependency.
Sep 2, 2016
75decb6
simple_endpoint.py: Encode payload as utf-8 byte object
Sep 3, 2016
82d44fe
Use string objects in str.format
Sep 3, 2016
03d34bd
Fix integer division in gp_hyper_opt_test.
Sep 3, 2016
874d99b
Fix another integer division which was the last failing unit test.
Sep 3, 2016
e9bceda
Check for a proper Boost python component in CMake file.
Sep 3, 2016
6159f1f
Replace WebError and Paste deps with pyramid_debugtoolbar and waitress
Sep 3, 2016
e2704b7
Update installation document and conda moe recipe about Python 3 supp…
Sep 3, 2016
34850d5
Enable Python 3 travis tests.
Sep 3, 2016
64e79fe
Add mistalenly removed webtest dependency to conda recipe.
Sep 4, 2016
4401dea
travis: Install python3-dev if default python is python3
Sep 4, 2016
397c6d4
Use another Precise PPA in order to update to cmake 3
Sep 4, 2016
69a7f28
Travis: Try another cmake3 Ubuntu precise ppa
Sep 4, 2016
1d39f4e
Update pytest version to fix pytest issue 744
Sep 4, 2016
516af2d
Add <numeric> to fix std::iota compile error in latest versions of gcc.
gokceneraslan Sep 4, 2016
916bf9d
Fix Boost detection in CMakeLists.txt
Sep 4, 2016
1b26ec4
CMakeLists.txt: Try out other libboost_python versions.
Sep 4, 2016
a4aba18
Remove some unnecessary list conversions introduced by the futurize s…
Sep 6, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
language: python
python:
- "2.7"
- "3.5"
cache:
- apt
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y build-essential libblas-dev liblapack-dev gfortran make libedit-dev
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test && sudo apt-get update -q && sudo apt-get install -y gcc-4.7 g++-4.7 && sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.7 20 && sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.7 20 && sudo update-alternatives --config gcc && sudo update-alternatives --config g++
- sudo add-apt-repository -y ppa:boost-latest/ppa && sudo apt-get update -q && sudo apt-get install -y boost1.55
- sudo add-apt-repository -y ppa:kalakris/cmake && sudo apt-get update -q && sudo apt-get install -y cmake
- sudo add-apt-repository -y ppa:roblib/ppa && sudo apt-get update -q && sudo apt-get install -y cmake cmake-data
- sudo add-apt-repository -y ppa:chris-lea/python-numpy && sudo apt-get update -q && sudo apt-get install -y python-numpy
- wget http://ppa.launchpad.net/libreoffice/ppa/ubuntu/pool/main/d/doxygen/doxygen_1.8.7-2~precise1_amd64.deb -O doxygen.deb
- sudo dpkg -i doxygen.deb
Expand Down
2 changes: 2 additions & 0 deletions conda-recipe/moe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ requirements:
- scipy
- pyramid
- pyramid_mako
- pyramid_debugtoolbar
- waitress
- colander
- libstdcplusplus
- simplejson
Expand Down
41 changes: 26 additions & 15 deletions development.ini
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
[app:MOE]
###
# app configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###

[app:main]
use = egg:MOE
reload_templates = true
debug_authorization = false
debug_notfound = false
debug_routematch = false
debug_templates = true
default_locale_name = en
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes =
pyramid_debugtoolbar

# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
use_mongo = false
mongodb.url = mongodb://localhost
mongodb.port = 27017
mongodb.db_name = mydb

[pipeline:main]
pipeline =
egg:WebError#evalerror
MOE
###
# wsgi server configuration
###

[server:main]
use = egg:Paste#http
use = egg:waitress#main
host = 0.0.0.0
port = 6543

# Begin logging configuration
###
# logging configuration
# http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###


[loggers]
keys = root, moe
Expand All @@ -38,7 +50,6 @@ args = ('%(here)s/moe.log','a')
level = DEBUG
formatter = generic

# used by all applications in the Pyramid process that ask for a logger
[logger_root]
level = INFO
handlers = console, filelog
Expand Down
6 changes: 3 additions & 3 deletions docs/cpp_rst_maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,13 @@ def create_rst_file(file_base, files):
)
index_count = 1
for file_type in CPP_FILE_TYPES:
if files.has_key(file_type):
if file_type in files:
fout.write(' {0:d}. `{1:s}`_\n'.format(index_count, files[file_type]))
index_count += 1
fout.write('\n')

for file_type in CPP_FILE_TYPES:
if files.has_key(file_type):
if file_type in files:
fout.write("""
{0:s}
{1:s}
Expand All @@ -102,7 +102,7 @@ def create_rst_files_for_cpp():
"""Generate all rst files."""
cpp_files = get_cpp_files()
create_cpp_tree(cpp_files)
for file_base, files in cpp_files.iteritems():
for file_base, files in cpp_files.items():
create_rst_file(file_base, files)


Expand Down
2 changes: 1 addition & 1 deletion docs/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ To ensure consistency, be sure to use full paths throughout the installation.

Requires:

1. ``python 2.6.7+`` - http://python.org/download/
1. ``python 2.6.7+`` or ``python 3.3+`` - http://python.org/download/
2. ``gcc 4.7.3+`` - http://gcc.gnu.org/install/
3. ``cmake 2.8.9+`` - http://www.cmake.org/cmake/help/install.html
4. ``boost 1.51+`` - http://www.boost.org/users/download/
Expand Down
5 changes: 3 additions & 2 deletions moe/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Base pyramid app for MOE."""
from __future__ import print_function
from pyramid.config import Configurator

from moe.resources import Root
Expand Down Expand Up @@ -44,7 +45,7 @@ def main(global_config, **settings):
app = config.make_wsgi_app()

# Message to the user
print """
print("""
Congratulations! MOE is now running.

You can access the web interface at: http://localhost:6543
Expand All @@ -54,6 +55,6 @@ def main(global_config, **settings):

Note: If you installed MOE within a docker container you may need to specify the IP address of the VM instead of localhost.
In OSX and Windows this is the startup information when you run boot2docker, or can be set in $DOCKER_HOST.
"""
""")

return app
10 changes: 5 additions & 5 deletions moe/bandit/bandit_interface.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
r"""Interface for Bandit functions, supports allocate arms and choose arm."""
from builtins import object
import numpy

from abc import ABCMeta, abstractmethod
from future.utils import with_metaclass


class BanditInterface(object):
class BanditInterface(with_metaclass(ABCMeta, object)):

r"""Interface for a bandit algorithm.

Expand All @@ -16,8 +18,6 @@ class BanditInterface(object):

"""

__metaclass__ = ABCMeta

@abstractmethod
def allocate_arms(self):
r"""Compute the allocation to each arm given ``historical_info``, running bandit `subtype`` endpoint with hyperparameters in `hyperparameter_info``.
Expand Down Expand Up @@ -46,9 +46,9 @@ def choose_arm(arms_to_allocations):
if not arms_to_allocations:
raise ValueError('arms_to_allocations is empty!')

allocations = numpy.array(arms_to_allocations.values())
allocations = numpy.array(list(arms_to_allocations.values()))
# The winning arm is chosen based on the distribution of arm allocations.
winner = numpy.argmax(numpy.random.dirichlet(allocations))
# While the internal order of a dict is unknowable a priori, the order presented by the various iterators
# and list-ify methods is always the same as long as the dict is not modified between calls to these methods.
return arms_to_allocations.keys()[winner]
return list(arms_to_allocations.keys())[winner]
4 changes: 2 additions & 2 deletions moe/bandit/bla/bla.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(
self._historical_info = copy.deepcopy(historical_info)
self._subtype = subtype
# Validate that every arm is a Bernoulli arm.
for arm in self._historical_info.arms_sampled.itervalues():
for arm in self._historical_info.arms_sampled.values():
if not isinstance(arm, BernoulliArm):
raise ValueError('All arms have to be Bernoulli arms!')

Expand Down Expand Up @@ -122,5 +122,5 @@ def get_winning_arm_names(self, arms_sampled):
if not arms_sampled:
raise ValueError('arms_sampled is empty!')

bla_payoff_arm_name_list = [(self.get_bla_payoff(sampled_arm), arm_name) for arm_name, sampled_arm in arms_sampled.iteritems()]
bla_payoff_arm_name_list = [(self.get_bla_payoff(sampled_arm), arm_name) for arm_name, sampled_arm in arms_sampled.items()]
return get_winning_arm_names_from_payoff_arm_name_list(bla_payoff_arm_name_list)
7 changes: 4 additions & 3 deletions moe/bandit/data_containers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Data containers convenient for/used to interact with bandit members."""
from builtins import object
import pprint

import numpy
Expand Down Expand Up @@ -202,7 +203,7 @@ def __str__(self, pretty_print=True):
def json_payload(self):
"""Construct a json serializeable and MOE REST recognizeable dictionary of the historical data."""
json_arms_sampled = {}
for name, arm in self._arms_sampled.iteritems():
for name, arm in self._arms_sampled.items():
json_arms_sampled[name] = arm.json_payload()
return {'arms_sampled': json_arms_sampled}

Expand All @@ -217,7 +218,7 @@ def validate_sample_arms(sample_arms):

"""
if sample_arms:
for arm in sample_arms.itervalues():
for arm in sample_arms.values():
arm.validate()

def append_sample_arms(self, sample_arms, validate=True):
Expand Down Expand Up @@ -246,7 +247,7 @@ def _update_historical_data(self, sample_arms):
:param sample_arms: the already-sampled arms: wins, losses, and totals
:type sample_arms: dictionary of (arm name, SampleArm) key-value pairs
"""
for name, arm in sample_arms.iteritems():
for name, arm in sample_arms.items():
if name in self._arms_sampled:
self._arms_sampled[name] += arm
else:
Expand Down
2 changes: 1 addition & 1 deletion moe/bandit/epsilon/epsilon_first.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def allocate_arms(self):
if not arms_sampled:
raise ValueError('sample_arms is empty!')

num_sampled = sum([sampled_arm.total for sampled_arm in arms_sampled.itervalues()])
num_sampled = sum([sampled_arm.total for sampled_arm in arms_sampled.values()])
# Exploration phase, trials 1,2,..., epsilon * T
# Allocate equal probability to all arms
if num_sampled < self._total_samples * self._epsilon:
Expand Down
3 changes: 2 additions & 1 deletion moe/bandit/epsilon/epsilon_greedy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
See :class:`moe.bandit.epsilon.epsilon_interface.EpsilonInterface` for further details on this bandit.

"""
from __future__ import division
from moe.bandit.constant import DEFAULT_EPSILON, EPSILON_SUBTYPE_GREEDY
from moe.bandit.epsilon.epsilon_interface import EpsilonInterface

Expand Down Expand Up @@ -75,7 +76,7 @@ def allocate_arms(self):
arms_to_allocations = {}

# With probability epsilon, choose a winning arm at random. Therefore, we split the allocation epsilon among all arms.
for arm_name in arms_sampled.iterkeys():
for arm_name in arms_sampled.keys():
arms_to_allocations[arm_name] = epsilon_allocation

# With probability 1-epsilon, split allocation among winning arms.
Expand Down
3 changes: 2 additions & 1 deletion moe/bandit/epsilon/epsilon_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
See :mod:`moe.bandit.bandit_interface` for further details on bandit.

"""
from __future__ import division
import copy

import numpy
Expand Down Expand Up @@ -62,7 +63,7 @@ def get_winning_arm_names(arms_sampled):
raise ValueError('arms_sampled is empty!')

avg_payoff_arm_name_list = []
for arm_name, sampled_arm in arms_sampled.iteritems():
for arm_name, sampled_arm in arms_sampled.items():
avg_payoff = numpy.float64(sampled_arm.win - sampled_arm.loss) / sampled_arm.total if sampled_arm.total > 0 else 0
avg_payoff_arm_name_list.append((avg_payoff, arm_name))

Expand Down
1 change: 1 addition & 0 deletions moe/bandit/ucb/ucb1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
See :class:`moe.bandit.ucb.ucb_interface.UCBInterface` for further details on this bandit.

"""
from __future__ import division
import math

import numpy
Expand Down
1 change: 1 addition & 0 deletions moe/bandit/ucb/ucb1_tuned.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
See :class:`moe.bandit.ucb.ucb_interface.UCBInterface` for further details on this bandit.

"""
from __future__ import division
import math

import numpy
Expand Down
6 changes: 3 additions & 3 deletions moe/bandit/ucb/ucb_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def get_unsampled_arm_names(arms_sampled):
if not arms_sampled:
raise ValueError('arms_sampled is empty!')

unsampled_arm_name_list = [name for name, sampled_arm in arms_sampled.iteritems() if sampled_arm.total == 0]
unsampled_arm_name_list = [name for name, sampled_arm in arms_sampled.items() if sampled_arm.total == 0]
return frozenset(unsampled_arm_name_list)

@abstractmethod
Expand Down Expand Up @@ -131,8 +131,8 @@ def get_winning_arm_names(self, arms_sampled):
if unsampled_arm_names:
return unsampled_arm_names

number_sampled = sum([sampled_arm.total for sampled_arm in arms_sampled.itervalues()])
number_sampled = sum([sampled_arm.total for sampled_arm in arms_sampled.values()])

ucb_payoff_arm_name_list = [(self.get_ucb_payoff(sampled_arm, number_sampled), arm_name) for arm_name, sampled_arm in arms_sampled.iteritems()]
ucb_payoff_arm_name_list = [(self.get_ucb_payoff(sampled_arm, number_sampled), arm_name) for arm_name, sampled_arm in arms_sampled.items()]

return get_winning_arm_names_from_payoff_arm_name_list(ucb_payoff_arm_name_list)
13 changes: 8 additions & 5 deletions moe/bandit/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
"""Utilities for bandit."""
from __future__ import division
from builtins import map
from builtins import zip


def get_winning_arm_names_from_payoff_arm_name_list(payoff_arm_name_list):
Expand All @@ -20,10 +23,10 @@ def get_winning_arm_names_from_payoff_arm_name_list(payoff_arm_name_list):
best_payoff, _ = max(payoff_arm_name_list)

# Filter out arms that have payoff less than the best payoff
winning_arm_payoff_name_list = filter(lambda payoff_arm_name: payoff_arm_name[0] == best_payoff, payoff_arm_name_list)
winning_arm_payoff_name_list = [payoff_arm_name for payoff_arm_name in payoff_arm_name_list if payoff_arm_name[0] == best_payoff]
# Extract a list of winning arm names from a list of (payoff, arm name) tuples.
_, winning_arm_name_list = map(list, zip(*winning_arm_payoff_name_list))
winning_arm_names = frozenset(winning_arm_name_list)
_, winning_arm_name_iter = map(list, zip(*winning_arm_payoff_name_list))
winning_arm_names = frozenset(winning_arm_name_iter)
return winning_arm_names


Expand All @@ -46,14 +49,14 @@ def get_equal_arm_allocations(arms_sampled, winning_arm_names=None):

# If no ``winning_arm_names`` given, split allocations among ``arms_sampled``.
if winning_arm_names is None:
winning_arm_names = frozenset([arm_name for arm_name in arms_sampled.iterkeys()])
winning_arm_names = frozenset([arm_name for arm_name in arms_sampled.keys()])

num_winning_arms = len(winning_arm_names)
arms_to_allocations = {}

winning_arm_allocation = 1.0 / num_winning_arms
# Split allocation among winning arms, all other arms get allocation of 0.
for arm_name in arms_sampled.iterkeys():
for arm_name in arms_sampled.keys():
arms_to_allocations[arm_name] = winning_arm_allocation if arm_name in winning_arm_names else 0.0

return arms_to_allocations
1 change: 1 addition & 0 deletions moe/easy_interface/experiment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
"""Classes for MOE optimizable experiments."""
from builtins import object
import pprint

from moe.optimal_learning.python.data_containers import HistoricalData
Expand Down
11 changes: 7 additions & 4 deletions moe/easy_interface/simple_endpoint.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
"""Simple functions for hitting the REST endpoints of a MOE service."""
from future import standard_library
standard_library.install_aliases()
import contextlib
import urllib2
import urllib.request, urllib.error, urllib.parse

import simplejson as json

Expand All @@ -20,8 +22,8 @@ def call_endpoint_with_payload(rest_host, rest_port, endpoint, json_payload, tes
"""Send a POST request to a ``url`` with a given ``json_payload``, return the response as a dict."""
if testapp is None:
url = "http://{0}:{1:d}{2}".format(rest_host, rest_port, endpoint)
request = urllib2.Request(url, json_payload, {'Content-Type': 'application/json'})
with contextlib.closing(urllib2.urlopen(request)) as f:
request = urllib.request.Request(url, json_payload, {'Content-Type': 'application/json'})
with contextlib.closing(urllib.request.urlopen(request)) as f:
response = f.read()
else:
response = testapp.post(endpoint, json_payload).body
Expand Down Expand Up @@ -49,7 +51,8 @@ def gp_next_points(
if 'domain_info' not in raw_payload:
raw_payload['domain_info'] = experiment_payload.get('domain_info')

json_payload = json.dumps(raw_payload)
#payload must be a byte object
json_payload = json.dumps(raw_payload).encode('utf-8')

json_response = call_endpoint_with_payload(rest_host, rest_port, endpoint, json_payload, testapp)

Expand Down
Loading