diff --git a/Auto_Run.py b/Auto_Run.py new file mode 100644 index 0000000..f86d14d --- /dev/null +++ b/Auto_Run.py @@ -0,0 +1,148 @@ +import pandas as pd +import numpy as np +import yaml +from sklearn.neighbors import KNeighborsClassifier +from sklearn.model_selection import train_test_split +import matplotlib.pyplot as plt + +from FS.pso import jfs as jfs_pso +from FS.ga import jfs as jfs_ga +from FS.de import jfs as jfs_de +from FS.ba import jfs as jfs_ba +from FS.cs import jfs as jfs_cs +from FS.fa import jfs as jfs_fa +from FS.fpa import jfs as jfs_fpa +from FS.sca import jfs as jfs_sca +from FS.woa import jfs as jfs_woa + + +class DataPipeline(object): + """ + Data Model with Pipeline to Process and Feature Select. + """ + def __init__(self, _ori_data_path:str, _train_test_ratio:float, _str_read_meta_para:str) -> None: + """ + para1:_ori_data_path:Read_RawData_Path(CSV_Format) => Convert to DataFrame + para2:_train_test_ratio:Split DataSet Ratio + """ + self._ori_data_path = _ori_data_path + self._train_test_ratio = _train_test_ratio + self._str_read_meta_para = _str_read_meta_para + + self.data = None + self.feat = None + self.label = None + + self.fold = None # Contain Splited Train/Test Data + self.meta_para = None + + self.sf = None # Select_Feature + self.fmdl = None # Feature Model + + def _load_ori_file(self, _ori_path) -> None: + ''' + Load Raw Data File By CSV Path + ''' + self.data = pd.read_csv(_ori_path) + self.data = self.data.values + #All Feature + self.feat = np.asarray(self.data[:, 0:-1]) + #Predict Y Value + self.label = np.asarray(self.data[:, -1]) + + def _data_split(self, ratio:float) -> None: + ''' + Setting Train/Test Ratio + ''' + xtrain, xtest, ytrain, ytest = train_test_split(self.feat, self.label, test_size=ratio, stratify=self.label) + self.fold = {'xt':xtrain, 'yt':ytrain, 'xv':xtest, 'yv':ytest} + + def _read_algo_parameter(self, _str_read_meta_para:str) ->None: + ''' + Read Meta Para File + ''' + with open('algo_para.yaml', 'r') as _str_read_meta_para: + self.meta_para = yaml.full_load(_str_read_meta_para)['meta_para'] + + def _build_fmdl(self, algo_name): + ''' + Construct Each Meta Model + ''' + interface_fmdl = {'pso': jfs_pso,'ga':jfs_ga, 'de':jfs_de, 'ba':jfs_ba, 'cs':jfs_cs, 'fa': jfs_fa, 'fpa':jfs_fpa, 'sca':jfs_sca, 'woa':jfs_woa} + return interface_fmdl[algo_name] + + def _process_feature_select(self, strMetaName:str, listMetaOpts:list) -> None: + """ + Prepare MetaModel and X Feature Data + """ + # perform feature selection + ## Append Fold Data Into OPTS + listMetaOpts['fold'] = self.fold + #self.fmdl = jfs_ga(self.feat, self.label, listMetaOpts) + self.fmdl = self._build_fmdl(strMetaName)(self.feat, self.label, listMetaOpts) + self.sf = self.fmdl['sf'] + + def _data_feature_select(self, strMetaName:str, listMetaOpts:list)->None: + """ + model with selected features + """ + num_train = np.size(self.fold['xt'], 0) + num_valid = np.size(self.fold['xv'], 0) + x_train = self.fold['xt'][:, self.sf] + y_train = self.fold['yt'].reshape(num_train) # Solve bug + x_valid = self.fold['xv'][:, self.sf] + y_valid = self.fold['yv'].reshape(num_valid) # Solve bug + + mdl = KNeighborsClassifier(n_neighbors = listMetaOpts['k']) + mdl.fit(x_train, y_train) + + # accuracy + y_pred = mdl.predict(x_valid) + Acc = np.sum(y_valid == y_pred) / num_valid + print("Accuracy:", 100 * Acc) + + # number of selected features + num_feat = self.fmdl['nf'] + print("Feature Size:", num_feat) + + def _plot_converege(self, strMetaName:str, listMetaOpts:list)-> None: + ''' + plot convergence + ''' + curve = self.fmdl['c'] + curve = curve.reshape(np.size(curve,1)) + x = np.arange(0, listMetaOpts['T'], 1.0) + 1.0 + fig, ax = plt.subplots() + ax.plot(x, curve, 'o-') + ax.set_xlabel('Number of Iterations') + ax.set_ylabel('Fitness') + ax.set_title(strMetaName) + ax.grid() + plt.show() + + def proceed(self)->None: + ''' + Execute Function + ''' + self._load_ori_file(self._ori_data_path) + self._data_split(self._train_test_ratio) + self._read_algo_parameter(self._str_read_meta_para) + + for meta in self.meta_para: + print(meta['name']) + print(meta['opts']) + self._process_feature_select(meta['name'], meta['opts']) + self._data_feature_select(meta['name'], meta['opts']) + self._plot_converege(meta['name'], meta['opts']) + +def main(): + str_read_file_path = './ionosphere.csv' + str_read_meta_para = './algo_para.yaml' + float_split_ratio = 0.3 + + auto_run = DataPipeline(str_read_file_path, float_split_ratio, str_read_meta_para) + auto_run.proceed() + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/FS/__basic.py b/FS/__basic.py new file mode 100644 index 0000000..8512751 --- /dev/null +++ b/FS/__basic.py @@ -0,0 +1,33 @@ +import numpy as np +from numpy.random import rand +from FS.__tran_func import tran_func + +def init_position(lb, ub, N, dim): + X = np.zeros([N, dim], dtype='float') + for i in range(N): + for d in range(dim): + X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() + + return X + +def binary_conversion(X, thres, N, dim, trans_func=tran_func.sl_trans): + Xbin = np.zeros([N, dim], dtype='int') + for i in range(N): + for d in range(dim): + X_trans = trans_func(X[i,d]) + if X_trans > thres: + Xbin[i,d] = 1 + else: + Xbin[i,d] = 0 + + return Xbin + + +def boundary(x, lb, ub): + if x < lb: + x = lb + if x > ub: + x = ub + + return x + diff --git a/FS/__tran_func.py b/FS/__tran_func.py new file mode 100644 index 0000000..af88323 --- /dev/null +++ b/FS/__tran_func.py @@ -0,0 +1,10 @@ +import numpy as np +class tran_func(): + + @staticmethod + def l_trans(val): + return val + + @staticmethod + def sl_trans(val): + return 1 / (1+np.exp(-2 * val)) \ No newline at end of file diff --git a/FS/ba.py b/FS/ba.py index 1ab3e80..41e3c96 100644 --- a/FS/ba.py +++ b/FS/ba.py @@ -3,42 +3,13 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x - +from FS.__basic import init_position, binary_conversion, boundary + def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 fmax = 2 # maximum frequency fmin = 0 # minimum frequency diff --git a/FS/cs.py b/FS/cs.py index da6998d..1001bd6 100644 --- a/FS/cs.py +++ b/FS/cs.py @@ -3,39 +3,10 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun +from FS.__basic import init_position, binary_conversion, boundary import math -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x - - # Levy Flight def levy_distribution(beta, dim): # Sigma @@ -54,8 +25,8 @@ def levy_distribution(beta, dim): def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 Pa = 0.25 # discovery rate alpha = 1 # constant diff --git a/FS/de.py b/FS/de.py index 9903258..77019b2 100644 --- a/FS/de.py +++ b/FS/de.py @@ -3,42 +3,13 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x +from FS.__basic import init_position, binary_conversion, boundary def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 CR = 0.9 # crossover rate F = 0.5 # factor diff --git a/FS/fa.py b/FS/fa.py index 85cb0bf..792c390 100644 --- a/FS/fa.py +++ b/FS/fa.py @@ -3,42 +3,13 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x +from FS.__basic import init_position, binary_conversion, boundary def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 alpha = 1 # constant beta0 = 1 # light amplitude diff --git a/FS/fpa.py b/FS/fpa.py index f21a6ec..7627c60 100644 --- a/FS/fpa.py +++ b/FS/fpa.py @@ -3,37 +3,11 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun -import math - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - +from FS.__basic import init_position, binary_conversion, boundary -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin +import math -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x # Levy Flight @@ -54,8 +28,8 @@ def levy_distribution(beta, dim): def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 beta = 1.5 # levy component P = 0.8 # switch probability diff --git a/FS/ga.py b/FS/ga.py index ed48729..2ba48a9 100644 --- a/FS/ga.py +++ b/FS/ga.py @@ -1,28 +1,7 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - +from FS.__basic import init_position, binary_conversion, boundary def roulette_wheel(prob): num = len(prob) @@ -38,8 +17,8 @@ def roulette_wheel(prob): def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 CR = 0.8 # crossover rate MR = 0.01 # mutation rate diff --git a/FS/gwo.py b/FS/gwo.py index 0bdb35c..7244fb3 100644 --- a/FS/gwo.py +++ b/FS/gwo.py @@ -3,42 +3,13 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun +from FS.__basic import init_position, binary_conversion, boundary -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x - - def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 N = opts['N'] diff --git a/FS/hho.py b/FS/hho.py index e54c64c..ec49e4f 100644 --- a/FS/hho.py +++ b/FS/hho.py @@ -3,39 +3,10 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun +from FS.__basic import init_position, binary_conversion, boundary import math -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x - - def levy_distribution(beta, dim): # Sigma nume = math.gamma(1 + beta) * np.sin(np.pi * beta / 2) @@ -53,8 +24,8 @@ def levy_distribution(beta, dim): def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 beta = 1.5 # levy component diff --git a/FS/ja.py b/FS/ja.py index 3592a10..48bdd1f 100644 --- a/FS/ja.py +++ b/FS/ja.py @@ -3,42 +3,14 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun +from FS.__basic import init_position, binary_conversion, boundary - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 N = opts['N'] diff --git a/FS/pso.py b/FS/pso.py index 495e11d..7491c9d 100644 --- a/FS/pso.py +++ b/FS/pso.py @@ -1,15 +1,7 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X +from FS.__basic import init_position, binary_conversion, boundary def init_velocity(lb, ub, N, dim): @@ -26,33 +18,12 @@ def init_velocity(lb, ub, N, dim): V[i,d] = Vmin[0,d] + (Vmax[0,d] - Vmin[0,d]) * rand() return V, Vmax, Vmin - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 w = 0.9 # inertia weight c1 = 2 # acceleration factor diff --git a/FS/sca.py b/FS/sca.py index 9e1616e..6c3418e 100644 --- a/FS/sca.py +++ b/FS/sca.py @@ -3,42 +3,13 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x +from FS.__basic import init_position, binary_conversion, boundary def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 alpha = 2 # constant diff --git a/FS/ssa.py b/FS/ssa.py index 53f8cce..df5841c 100644 --- a/FS/ssa.py +++ b/FS/ssa.py @@ -3,42 +3,14 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun +from FS.__basic import init_position, binary_conversion, boundary -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x - def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 N = opts['N'] diff --git a/FS/woa.py b/FS/woa.py index a6f7a8f..403324e 100644 --- a/FS/woa.py +++ b/FS/woa.py @@ -3,42 +3,12 @@ import numpy as np from numpy.random import rand from FS.functionHO import Fun - - -def init_position(lb, ub, N, dim): - X = np.zeros([N, dim], dtype='float') - for i in range(N): - for d in range(dim): - X[i,d] = lb[0,d] + (ub[0,d] - lb[0,d]) * rand() - - return X - - -def binary_conversion(X, thres, N, dim): - Xbin = np.zeros([N, dim], dtype='int') - for i in range(N): - for d in range(dim): - if X[i,d] > thres: - Xbin[i,d] = 1 - else: - Xbin[i,d] = 0 - - return Xbin - - -def boundary(x, lb, ub): - if x < lb: - x = lb - if x > ub: - x = ub - - return x - +from FS.__basic import init_position, binary_conversion, boundary def jfs(xtrain, ytrain, opts): # Parameters - ub = 1 - lb = 0 + ub = opts['ub'] + lb = opts['lb'] thres = 0.5 b = 1 # constant diff --git a/README.md b/README.md index b5c363a..a8df18d 100644 --- a/README.md +++ b/README.md @@ -215,4 +215,7 @@ plt.show() | 01 | `ga` | [Genetic Algorithm](/Description.md#genetic-algorithm-ga) | - | Yes | - +## Auto run +* You can run multiple-model in one python code(Auto_Run.py), compare with model result. +* Model List: pso, ga, de, ba, cs, fa, fpa, sca, woa +* Setting File: algo_para.yaml diff --git a/algo_para.yaml b/algo_para.yaml new file mode 100644 index 0000000..dfe7c12 --- /dev/null +++ b/algo_para.yaml @@ -0,0 +1,43 @@ +meta_para: ## Ref: https://github.com/tongysmember/Wrapper-Feature-Selection-Toolbox-Python/blob/main/Description.md#differential-evolution-de +- name: pso + # K : k-value in KNN + # N : number of particles + # T : maximum number of iterations + opts: {'k': 5, 'N': 10, 'T': 10, 'ub': 1, 'lb': 0} +- name: ga + # K : k-value in KNN + # N : number of particles + # T : maximum number of iterations + # T + # MR + opts: {'k': 5, 'N': 10, 'T': 10, 'CR': 0.8, 'MR': 0.01, 'ub': 1, 'lb': 0} +- name: de + # CR : crossover rate + # F : constant factor + opts: {'k': 5, 'N': 10, 'T': 10, 'CR': 0.9, 'F': 0.5, 'ub': 1, 'lb': 0} +- name: ba + #fmax = 2 # maximum frequency + #fmin = 0 # minimum frequency + #alpha = 0.9 # constant + #gamma = 0.9 # constant + #A = 2 # maximum loudness + #r = 1 # maximum pulse rate + opts: {'k': 5, 'N': 10, 'T': 10, 'fmax': 2, 'fmin': 0, 'alpha': 0.9, 'gamma': 0.9, 'A': 2, 'r': 1, 'ub': 1, 'lb': 0} +- name: cs + #Pa = 0.25 # discovery rate + opts: {'k': 5, 'N': 10, 'T': 10, 'Pa': 0.25, 'ub': 1, 'lb': 0} +- name: fa + #alpha = 1 # constant + #beta0 = 1 # light amplitude + #gamma = 1 # absorbtion coefficient + #theta = 0.97 # control alpha + opts: {'k': 5, 'N': 10, 'T': 10, 'alpha': 1, 'beta0': 1, 'gamma': 1, 'theta': 0.97, 'ub': 1, 'lb': 0} +- name: fpa + #P = 0.8 # switch probability + opts: {'k': 5, 'N': 10, 'T': 10, 'P': 0.8, 'ub': 1, 'lb': 0} +- name: sca + #alpha = 2 # constant + opts: {'k': 5, 'N': 10, 'T': 10, 'alpha': 2, 'ub': 1, 'lb': 0} +- name: woa + #b = 1 # constant + opts: {'k': 5, 'N': 10, 'T': 10, 'b': 1, 'ub': 1, 'lb': 0} \ No newline at end of file