Skip to content

Commit

Permalink
Merge branch 'main' into gh-pages
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasBoTang committed Apr 24, 2024
2 parents 6162209 + 84c96fb commit d64ce3b
Show file tree
Hide file tree
Showing 17 changed files with 247 additions and 98 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/python-package-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Python Package using Conda
on:
release:
types: [published]
workflow_dispatch:

jobs:
build-linux:
Expand All @@ -13,7 +14,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup Miniconda
uses: conda-incubator/setup-miniconda@v2
uses: conda-incubator/setup-miniconda@v3
with:
activate-environment: pyepo-build
python-version: 3.9
Expand All @@ -22,11 +23,15 @@ jobs:
run: conda install conda-build anaconda-client
- name: Build Conda Package
run: conda build ./pkg -c conda-forge -c pytorch -c gurobi -c default
- name: Find and upload Conda package
- name: Extract Package Version
run: |
PACKAGE_VERSION=$(conda search --json -c local pyepo | jq -r '.pyepo[-1].version')
echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> $GITHUB_ENV
- name: Find and Upload Conda Package
env:
ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
run: |
CONDA_BLD_PATH=$(conda info --json | jq -r '.conda_prefix')/conda-bld
echo "Conda build path: $CONDA_BLD_PATH"
conda install -n pyepo-build anaconda-client --yes
conda run -n pyepo-build anaconda upload --user pyepo $CONDA_BLD_PATH/*/*.tar.bz2
conda run -n pyepo-build anaconda upload --user pyepo $CONDA_BLD_PATH/noarch/pyepo-${{ env.PACKAGE_VERSION }}-py_0.tar.bz2
1 change: 1 addition & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ name: Upload Python Package
on:
release:
types: [published]
workflow_dispatch:

permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion pkg/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

package:
name: pyepo
version: 0.3.6
version: 0.3.8

source:
path: ./
Expand Down
2 changes: 1 addition & 1 deletion pkg/pyepo/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
Synthetic data generation
"""

from pyepo.data import dataset, shortestpath, knapsack, tsp
from pyepo.data import dataset, shortestpath, knapsack, tsp, portfolio
56 changes: 56 additions & 0 deletions pkg/pyepo/data/portfolio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python
# coding: utf-8
"""
Synthetic data for portfolio
"""

import numpy as np


def genData(num_data, num_features, num_assets, deg=1, noise_level=1, seed=135):
"""
A function to generate synthetic data and features for travelling salesman
Args:
num_data (int): number of data points
num_features (int): dimension of features
num_assets (int): number of assets
deg (int): data polynomial degree
noise_level (float): level of data random noise
seed (int): random seed
Returns:
tuple: data features (np.ndarray), costs (np.ndarray)
"""
# positive integer parameter
if type(deg) is not int:
raise ValueError("deg = {} should be int.".format(deg))
if deg <= 0:
raise ValueError("deg = {} should be positive.".format(deg))
# set seed
rnd = np.random.RandomState(seed)
# number of data points
n = num_data
# dimension of features
p = num_features
# number of assets
m = num_assets
# random matrix parameter B
B = rnd.binomial(1, 0.5, (m, p))
# random matrix parameter L
L = rnd.uniform(-2.5e-3*noise_level, 2.5e-3*noise_level, (num_assets, num_features))
# feature vectors
x = rnd.normal(0, 1, (n, p))
# value of items
r = np.zeros((n, m))
for i in range(n):
# mean return of assets
r[i] = (0.05 * np.dot(B, x[i].reshape(p, 1)).T / np.sqrt(p) + \
0.1 ** (1 / deg)) ** deg
# random noise
f = rnd.randn(num_features)
eps = rnd.randn(num_assets)
r[i] += L @ f + 0.01 * noise_level * eps
# covariance matrix of the returns
cov = L @ L.T + (1e-2 * noise_level) * np.eye(num_assets)
return cov, x, r
9 changes: 9 additions & 0 deletions pkg/pyepo/func/abcmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,12 @@ def forward(self, pred_cost, true_cost):
"""
# convert tensor
pass

def _update_solution_pool(self, sol):
"""
Add new solutions to solution pool
"""
# add into solpool
self.solpool = np.concatenate((self.solpool, sol))
# remove duplicate
self.solpool = np.unique(self.solpool, axis=0)
48 changes: 10 additions & 38 deletions pkg/pyepo/func/blackbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from pyepo.func.abcmodule import optModule
from pyepo import EPO
from pyepo.func.utlis import _solveWithObj4Par, _solve_in_pass, _cache_in_pass
from pyepo.func.utlis import _solve_or_cache


class blackboxOpt(optModule):
Expand Down Expand Up @@ -73,28 +73,18 @@ def forward(ctx, pred_cost, module):
"""
# get device
device = pred_cost.device
# convert tenstor
# convert tensor
cp = pred_cost.detach().to("cpu").numpy()
# solve
rand_sigma = np.random.uniform()
if rand_sigma <= module.solve_ratio:
sol, _ = _solve_in_pass(cp, module.optmodel, module.processes, module.pool)
if module.solve_ratio < 1:
# add into solpool
module.solpool = np.concatenate((module.solpool, sol))
# remove duplicate
module.solpool = np.unique(module.solpool, axis=0)
else:
sol, _ = _cache_in_pass(cp, module.optmodel, module.solpool)
sol, _ = _solve_or_cache(cp, module)
# convert to tensor
sol = np.array(sol)
pred_sol = torch.FloatTensor(sol).to(device)
sol = torch.FloatTensor(sol).to(device)
# save
ctx.save_for_backward(pred_cost, pred_sol)
ctx.save_for_backward(pred_cost, sol)
# add other objects to ctx
ctx.lambd = module.lambd
ctx.module = module
return pred_sol
return sol

@staticmethod
def backward(ctx, grad_output):
Expand All @@ -113,16 +103,7 @@ def backward(ctx, grad_output):
# perturbed costs
cq = cp + lambd * dl
# solve
rand_sigma = np.random.uniform()
if rand_sigma <= module.solve_ratio:
sol, _ = _solve_in_pass(cq, module.optmodel, module.processes, module.pool)
if module.solve_ratio < 1:
# add into solpool
module.solpool = np.concatenate((module.solpool, sol))
# remove duplicate
module.solpool = np.unique(module.solpool, axis=0)
else:
sol, _ = _cache_in_pass(cq, module.optmodel, module.solpool)
sol, _ = _solve_or_cache(cq, module)
# get gradient
grad = (sol - wp) / module.lambd
# convert to tensor
Expand Down Expand Up @@ -189,21 +170,12 @@ def forward(ctx, pred_cost, module):
# convert tenstor
cp = pred_cost.detach().to("cpu").numpy()
# solve
rand_sigma = np.random.uniform()
if rand_sigma <= module.solve_ratio:
sol, _ = _solve_in_pass(cp, module.optmodel, module.processes, module.pool)
if module.solve_ratio < 1:
# add into solpool
module.solpool = np.concatenate((module.solpool, sol))
# remove duplicate
module.solpool = np.unique(module.solpool, axis=0)
else:
sol, _ = _cache_in_pass(cp, module.optmodel, module.solpool)
sol, _ = _solve_or_cache(cp, module)
# convert to tensor
pred_sol = torch.FloatTensor(np.array(sol)).to(device)
sol = torch.FloatTensor(sol).to(device)
# add other objects to ctx
ctx.optmodel = module.optmodel
return pred_sol
return sol

@staticmethod
def backward(ctx, grad_output):
Expand Down
10 changes: 3 additions & 7 deletions pkg/pyepo/func/contrastive.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pyepo import EPO
from pyepo.func.abcmodule import optModule
from pyepo.data.dataset import optDataset
from pyepo.func.utlis import _solveWithObj4Par, _solve_in_pass, _cache_in_pass
from pyepo.func.utlis import _solveWithObj4Par, _solve_in_pass


class NCE(optModule):
Expand Down Expand Up @@ -53,9 +53,7 @@ def forward(self, pred_cost, true_sol):
if np.random.uniform() <= self.solve_ratio:
sol, _ = _solve_in_pass(cp, self.optmodel, self.processes, self.pool)
# add into solpool
self.solpool = np.concatenate((self.solpool, sol))
# remove duplicate
self.solpool = np.unique(self.solpool, axis=0)
self._update_solution_pool(sol)
solpool = torch.from_numpy(self.solpool.astype(np.float32)).to(device)
# get current obj
obj_cp = torch.einsum("bd,bd->b", pred_cost, true_sol).unsqueeze(1)
Expand Down Expand Up @@ -118,9 +116,7 @@ def forward(self, pred_cost, true_sol):
if np.random.uniform() <= self.solve_ratio:
sol, _ = _solve_in_pass(cp, self.optmodel, self.processes, self.pool)
# add into solpool
self.solpool = np.concatenate((self.solpool, sol))
# remove duplicate
self.solpool = np.unique(self.solpool, axis=0)
self._update_solution_pool(sol)
solpool = torch.from_numpy(self.solpool.astype(np.float32)).to(device)
# get current obj
obj_cp = torch.einsum("bd,bd->b", pred_cost, true_sol).unsqueeze(1)
Expand Down
7 changes: 2 additions & 5 deletions pkg/pyepo/func/perturbed.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,13 @@ def backward(ctx, grad_output):


def _solve_or_cache(ptb_c, module):
rand_sigma = np.random.uniform()
# solve optimization
if rand_sigma <= module.solve_ratio:
if np.random.uniform() <= module.solve_ratio:
ptb_sols = _solve_in_pass(ptb_c, module.optmodel, module.processes, module.pool)
if module.solve_ratio < 1:
sols = ptb_sols.reshape(-1, cp.shape[1])
# add into solpool
module.solpool = np.concatenate((module.solpool, sols))
# remove duplicate
module.solpool = np.unique(module.solpool, axis=0)
module._update_solution_pool(sol)
# best cached solution
else:
ptb_sols = _cache_in_pass(ptb_c, optmodel, module.solpool)
Expand Down
14 changes: 4 additions & 10 deletions pkg/pyepo/func/rank.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from pyepo import EPO
from pyepo.func.abcmodule import optModule
from pyepo.data.dataset import optDataset
from pyepo.func.utlis import _solveWithObj4Par, _solve_in_pass, _cache_in_pass
from pyepo.func.utlis import _solveWithObj4Par, _solve_in_pass


class listwiseLTR(optModule):
Expand Down Expand Up @@ -56,9 +56,7 @@ def forward(self, pred_cost, true_cost):
if np.random.uniform() <= self.solve_ratio:
sol, _ = _solve_in_pass(cp, self.optmodel, self.processes, self.pool)
# add into solpool
self.solpool = np.concatenate((self.solpool, sol))
# remove duplicate
self.solpool = np.unique(self.solpool, axis=0)
self._update_solution_pool(sol)
# convert tensor
solpool = torch.from_numpy(self.solpool.astype(np.float32)).to(device)
# obj for solpool
Expand Down Expand Up @@ -124,9 +122,7 @@ def forward(self, pred_cost, true_cost):
if np.random.uniform() <= self.solve_ratio:
sol, _ = _solve_in_pass(cp, self.optmodel, self.processes, self.pool)
# add into solpool
self.solpool = np.concatenate((self.solpool, sol))
# remove duplicate
self.solpool = np.unique(self.solpool, axis=0)
self._update_solution_pool(sol)
# convert tensor
solpool = torch.from_numpy(self.solpool.astype(np.float32)).to(device)
# obj for solpool
Expand Down Expand Up @@ -206,9 +202,7 @@ def forward(self, pred_cost, true_cost):
if np.random.uniform() <= self.solve_ratio:
sol, _ = _solve_in_pass(cp, self.optmodel, self.processes, self.pool)
# add into solpool
self.solpool = np.concatenate((self.solpool, sol))
# remove duplicate
self.solpool = np.unique(self.solpool, axis=0)
self._update_solution_pool(sol)
# convert tensor
solpool = torch.from_numpy(self.solpool.astype(np.float32)).to(device)
# obj for solpool as score
Expand Down
13 changes: 2 additions & 11 deletions pkg/pyepo/func/spoplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from pyepo import EPO
from pyepo.func.abcmodule import optModule
from pyepo.func.utlis import _solveWithObj4Par, _solve_in_pass, _cache_in_pass
from pyepo.func.utlis import _solve_or_cache

class SPOPlus(optModule):
"""
Expand Down Expand Up @@ -87,15 +87,7 @@ def forward(ctx, pred_cost, true_cost, true_sol, true_obj, module):
# check sol
#_check_sol(c, w, z)
# solve
if np.random.uniform() <= module.solve_ratio:
sol, obj = _solve_in_pass(2*cp-c, module.optmodel, module.processes, module.pool)
if module.solve_ratio < 1:
# add into solpool
module.solpool = np.concatenate((module.solpool, sol))
# remove duplicate
module.solpool = np.unique(module.solpool, axis=0)
else:
sol, obj = _cache_in_pass(2*cp-c, module.optmodel, module.solpool)
sol, obj = _solve_or_cache(2 * cp - c, module)
# calculate loss
loss = []
for i in range(len(cp)):
Expand All @@ -107,7 +99,6 @@ def forward(ctx, pred_cost, true_cost, true_sol, true_obj, module):
loss = - np.array(loss)
# convert to tensor
loss = torch.FloatTensor(loss).to(device)
sol = np.array(sol)
sol = torch.FloatTensor(sol).to(device)
# save solutions
ctx.save_for_backward(true_sol, sol)
Expand Down
19 changes: 19 additions & 0 deletions pkg/pyepo/func/utlis.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@
from pyepo.utlis import getArgs


def _solve_or_cache(cp, module):
"""
A function to get optimization solution in the forward/backward pass
"""
# solve optimization
if np.random.uniform() <= module.solve_ratio:
sol, obj = _solve_in_pass(cp, module.optmodel, module.processes, module.pool)
if module.solve_ratio < 1:
# add into solpool
module._update_solution_pool(sol)
# best cached solution
else:
sol, obj = _cache_in_pass(cp, module.optmodel, module.solpool)
return sol, obj


def _solve_in_pass(cp, optmodel, processes, pool):
"""
A function to solve optimization in the forward/backward pass
Expand All @@ -26,6 +42,9 @@ def _solve_in_pass(cp, optmodel, processes, pool):
solp, objp = optmodel.solve()
sol.append(solp)
obj.append(objp)
# to numpy
sol = np.array(sol)
obj = np.array(obj)
# multi-core
else:
# get class
Expand Down
1 change: 1 addition & 0 deletions pkg/pyepo/model/grb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from pyepo.model.grb.shortestpath import shortestPathModel
from pyepo.model.grb.knapsack import knapsackModel
from pyepo.model.grb.tsp import tspGGModel, tspDFJModel, tspMTZModel
from pyepo.model.grb.portfolio import portfolioModel
Loading

0 comments on commit d64ce3b

Please sign in to comment.