Skip to content

Commit 2600c40

Browse files
authored
Passing timelimit specified on command line to pyomo solver (#97)
* Passing timelimit specified on command line to pyomo solver; bug fix with cd; check-solver in docs * Added an entry for the check-solver command and an associated doc page
1 parent d722fb1 commit 2600c40

9 files changed

Lines changed: 41 additions & 17 deletions

File tree

docs/source/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ section of your `hatchet.ini` (if using the [hatchet run](doc_runhatchet.html) c
137137
solver. Make sure the relevant solver binaries are in your `$PATH`, otherwise Pyomo will not be able to find them
138138
correctly.
139139
140+
One HATCHet command that is very useful to sanity-check your solver is `hatchet check-solver`. This command runs the HATCHet `compute_cn` step on a small set of pre-packaged data files and completes fairly quickly (a few seconds for the Gurobi optimizer, but up to a few minutes for glpk). Running this command will ensure that you have your solver settings (including licenses) set up correctly, so you should always run this command first before trying out the `compute_cn` step on your large data files.
141+
140142
### Required data
141143
<a name="requireddata"></a>
142144

docs/source/doc_check_solver.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# check-solver
2+
3+
This command of HATCHet runs the `compute_cn` step on a set of small data files (.bbc/.seg) pre-packaged with HATCHet, and is a quick way to verify if your solver is working correctly.
4+
If you are unable to run this command, it likely indicates a licensing issue with default (Gurobi) solver. To use alternative solvers, see the
5+
[Using a different Pyomo-supported solver](README.html#usingasolver_other) section of the README for more details).
6+
7+
## Input
8+
9+
This command takes no inputs.
10+
11+
## Output
12+
13+
This command produces debugging output from the solver. Look for the message towards the end of the output:
14+
15+
```
16+
# Your current solver ... seems to be working correctly
17+
```
18+
19+
to verify if your selected solver works correctly.

docs/source/doc_compute_cn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# hatchet
1+
# compute-cn
22

33
This step computes allele-specific fractional copy numbers, solves a constrained distance-based simultaneous factorization to compute allele and clone-specific copy numbers and clone proportions, and deploys a model-selection criterion select the number of clone by explicitly considering the trade-off between subclonal copy-number aberrations and whole-genome duplication.
44
The step offers some parameters to control each of these features.

docs/source/doc_fullpipeline.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ hatchet <step>
6464
| (5) | [*plot-bins*](doc_plot_bins.html) | BBot | This step produces informative plots concerning the computed RDRs, BAFs, and clusters. The information produced by this step are important to validate the compute clusters of genomic regions. |
6565
| (6) | [*compute-cn*](doc_compute_cn.html) | hatchet | This step computes allele-specific fractional copy numbers, solves a constrained distance-based simultaneous factorization to compute allele and clone-specific copy numbers and clone proportions, and deploys a model-selection criterion select the number of clone by explicitly considering the trade-off between subclonal copy-number aberrations and whole-genome duplication. |
6666
| (7) | [*plot-cn*](doc_plot_cn.html) | BBeval | This step analyzes the inferred copy-number states and clone proportions and produces informative plots jointly considering all samples from the same patient. In addition, this step can also combine results obtained for different patients and perform integrative analysis. |
67+
| (8) | [*check-solver*](doc_check_solver.html) | | This step runs the `compute_cn` step on a set of small data files pre-packaged with HATCHet, and is a quick way to verify if your solver is working correctly. |
6768

6869
## Recommendations and quality control
6970
<a name="recommendations"></a>

src/hatchet/bin/HATCHet.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,8 @@ def execute_python(solver, args, n, outprefix):
634634
n_seed=args['p'],
635635
n_worker=args['j'],
636636
random_seed=args['r'],
637-
max_iters=args['f']
637+
max_iters=args['f'],
638+
timelimit=args['s']
638639
)
639640

640641
segmentation(cA, cB, u, cluster_ids, sample_ids, bbc_file=args['bbc'], bbc_out_file=bbc_out_file,

src/hatchet/utils/check_solver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def main(args=None):
2727
'-p', '5',
2828
'-v', '3',
2929
'-u', '0.03',
30-
'--mode', '1',
30+
'--mode', '0',
3131
'-r', '6700',
3232
'-j', '1',
3333
'-eD', '6',

src/hatchet/utils/solve/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def solver_available(solver=None):
1919

2020

2121
def solve(clonal, seg_file, n, solver='gurobi', solve_mode='cd', d=-1, cn_max=-1, mu=0.01, diploid_threshold=0.1,
22-
ampdel=True, n_seed=400, n_worker=8, random_seed=None, max_iters=None):
22+
ampdel=True, n_seed=400, n_worker=8, random_seed=None, max_iters=None, timelimit=None):
2323

2424
assert solve_mode in ('ilp', 'cd', 'both'), 'Unrecognized solve_mode'
2525
assert solver_available(solver), f'Solver {solver} not available or not licensed'
@@ -65,17 +65,17 @@ def solve(clonal, seg_file, n, solver='gurobi', solve_mode='cd', d=-1, cn_max=-1
6565
if solve_mode == 'ilp':
6666
ilp = ILPSubset(n, cn_max, d=d, mu=mu, ampdel=ampdel, copy_numbers=copy_numbers, f_a=f_a, f_b=f_b, w=weights)
6767
ilp.create_model()
68-
return ilp.run(solver_type=solver)
68+
return ilp.run(solver_type=solver, timelimit=timelimit)
6969
elif solve_mode == 'cd':
7070
cd = CoordinateDescent(f_a=f_a, f_b=f_b, n=n, mu=mu, d=d, cn_max=cn_max, w=weights, ampdel=ampdel,
7171
cn=copy_numbers)
72-
return cd.run(solver_type=solver, max_iters=max_iters, n_seed=n_seed, j=n_worker, random_seed=random_seed)
72+
return cd.run(solver_type=solver, max_iters=max_iters, n_seed=n_seed, j=n_worker, random_seed=random_seed, timelimit=timelimit)
7373
else:
7474
cd = CoordinateDescent(f_a=f_a, f_b=f_b, n=n, mu=mu, d=d, cn_max=cn_max, w=weights, ampdel=ampdel,
7575
cn=copy_numbers)
76-
_, cA, cB, _ = cd.run(solver_type=solver, max_iters=max_iters, n_seed=n_seed, j=n_worker, random_seed=random_seed)
76+
_, cA, cB, _, _, _ = cd.run(solver_type=solver, max_iters=max_iters, n_seed=n_seed, j=n_worker, random_seed=random_seed, timelimit=timelimit)
7777

7878
ilp = ILPSubset(n, cn_max, d=d, mu=mu, ampdel=ampdel, copy_numbers=copy_numbers, f_a=f_a, f_b=f_b, w=weights)
7979
ilp.create_model()
8080
ilp.hot_start(cA, cB)
81-
return ilp.run(solver_type=solver)
81+
return ilp.run(solver_type=solver, timelimit=timelimit)

src/hatchet/utils/solve/cd.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def __init__(self, ilp, solver):
1111
self.ilp = ilp
1212
self.solver_type = solver
1313

14-
def run(self, cA, cB, u, max_iters, max_convergence_iters, tol=0.001):
14+
def run(self, cA, cB, u, max_iters, max_convergence_iters, tol=0.001, timelimit=None):
1515
_iters = _convergence_iters = 0
1616
_u = u
1717
_cA, _cB = cA, cB # first hot-start values
@@ -22,15 +22,15 @@ def run(self, cA, cB, u, max_iters, max_convergence_iters, tol=0.001):
2222
carch.fix_u(_u)
2323
carch.create_model()
2424
carch.hot_start(_cA, _cB)
25-
carch_results = carch.run(self.solver_type)
25+
carch_results = carch.run(self.solver_type, timelimit=timelimit)
2626
if carch_results is None:
2727
return None
2828
_obj_c, _cA, _cB, _, _, _ = carch_results
2929

3030
uarch = copy(self.ilp)
3131
uarch.fix_c(_cA, _cB)
3232
uarch.create_model()
33-
uarch_results = uarch.run(self.solver_type)
33+
uarch_results = uarch.run(self.solver_type, timelimit=timelimit)
3434
if uarch_results is None:
3535
return None
3636
_obj_u, _, _, _u, _, _ = uarch_results
@@ -47,9 +47,9 @@ def run(self, cA, cB, u, max_iters, max_convergence_iters, tol=0.001):
4747

4848

4949
# Top-level 'work' function that can be pickled for multiprocessing
50-
def _work(cd, u, solver_type, max_iters, max_convergence_iters):
50+
def _work(cd, u, solver_type, max_iters, max_convergence_iters, timelimit):
5151
worker = Worker(cd.ilp, solver_type)
52-
return worker.run(cd.hcA, cd.hcB, u, max_iters=max_iters, max_convergence_iters=max_convergence_iters)
52+
return worker.run(cd.hcA, cd.hcB, u, max_iters=max_iters, max_convergence_iters=max_convergence_iters, timelimit=timelimit)
5353

5454

5555
class CoordinateDescent:
@@ -61,15 +61,15 @@ def __init__(self, f_a, f_b, n, mu, d, cn_max, cn, w, ampdel=True):
6161

6262
self.seeds = None
6363

64-
def run(self, solver_type='gurobi', max_iters=10, max_convergence_iters=2, n_seed=400, j=8, random_seed=None):
64+
def run(self, solver_type='gurobi', max_iters=10, max_convergence_iters=2, n_seed=400, j=8, random_seed=None, timelimit=None):
6565
with Random(random_seed):
6666
seeds = [self.ilp.build_random_u() for _ in range(n_seed)]
6767

6868
result = {} # obj. value => (cA, cB, u) mapping
6969
to_do = []
7070
with ProcessPoolExecutor(max_workers=min(j, n_seed)) as executor:
7171
for u in seeds:
72-
future = executor.submit(_work, self, u, solver_type, max_iters, max_convergence_iters)
72+
future = executor.submit(_work, self, u, solver_type, max_iters, max_convergence_iters, timelimit)
7373
to_do.append(future)
7474

7575
for future in as_completed(to_do):

src/hatchet/utils/solve/ilp_subset.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,9 @@ def run(self, solver_type='gurobi', timelimit=None, write_path=None):
538538
else:
539539
solver = pe.SolverFactory(solver_type)
540540

541-
timelimit = int(timelimit or config.compute_cn.timelimit or 100)
542-
kwargs = {'timelimit': timelimit, 'report_timing': False}
541+
kwargs = {'report_timing': False}
542+
if timelimit is not None:
543+
kwargs['timelimit'] = int(timelimit)
543544
if solver.warm_start_capable():
544545
kwargs['warmstart'] = self.warmstart
545546

0 commit comments

Comments
 (0)