-
Notifications
You must be signed in to change notification settings - Fork 7
Use Click to parse command-line arguments in lemke.py #10
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
Changes from all commits
fadb0a4
fd77634
76547ee
04a3e94
ab89698
f921095
9698add
2be8eed
bf78ff4
32dc4d8
ab263d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,8 +6,9 @@ build-backend = "setuptools.build_meta" | |
| name = "lemke" | ||
| version = "0.0.1" | ||
| dependencies = [ | ||
| "numpy>=2.2,<2.3", | ||
| "matplotlib>=3.10,<3.11" | ||
| "numpy>=2.2", | ||
| "matplotlib>=3.10", | ||
| "click>=8.1", | ||
| ] | ||
|
Comment on lines
8
to
12
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignoring this, since upper bounds were removed based on the earlier review. |
||
|
|
||
| requires-python = ">=3.10" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,49 +4,9 @@ | |
| import math # gcd | ||
| import sys | ||
|
|
||
| from . import columnprint, utils | ||
|
|
||
| # global defaults | ||
| lcpfilename = "lcp" | ||
| outfile = lcpfilename + ".out" | ||
| filehandle = sys.stdout | ||
| verbose = False | ||
| silent = False | ||
| z0 = False | ||
|
|
||
|
|
||
| # process command-line arguments | ||
| def processArguments(): | ||
| global lcpfilename, outfile, verbose, silent, z0 | ||
| helpstring = """usage: lemke.py [options] | ||
| options: -v, -verbose : printout intermediate tableaus | ||
| -s, -silent : send output to <lcpfilename>.out | ||
| -z0 : show value of z0 at each step | ||
| -?, -help: show this help | ||
| <lcpfilename> (default: "lcp", must not start with "-") | ||
| Example: [python] lemke.py -v 2lcp""" | ||
| arglist = sys.argv[1:] | ||
| showhelp = False | ||
| for s in arglist: | ||
| if s == "-v" or s == "-verbose": | ||
| verbose = True | ||
| elif s == "-s" or s == "-silent": | ||
| silent = True | ||
| elif s == "-z0": | ||
| z0 = True | ||
| elif s[0] == "-": | ||
| showhelp = True | ||
| else: | ||
| lcpfilename = s | ||
| outfile = s + ".out" | ||
| if showhelp: | ||
| printout(helpstring) | ||
| exit(0) | ||
| return | ||
|
|
||
| import click | ||
|
|
||
| def printout(*s): | ||
| print(*s, file=filehandle) | ||
| from . import columnprint, utils | ||
|
|
||
|
|
||
| # LCP data M,q,d | ||
|
|
@@ -70,9 +30,9 @@ def __init__(self, arg): | |
| # flatten into words | ||
| words = utils.towords(lines) | ||
| if words[0] != "n=": | ||
| printout("lcp file", repr(filename), | ||
| "must start with 'n=' lcpdim, e.g. 'n= 5', not", | ||
| repr(words[0])) | ||
| print("lcp file", repr(filename), | ||
| "must start with 'n=' lcpdim, e.g. 'n= 5', not", | ||
| repr(words[0])) | ||
| exit(1) | ||
| n = int(words[1]) | ||
| self.n = n | ||
|
|
@@ -87,9 +47,9 @@ def __init__(self, arg): | |
| needfracs = n * n + 2 * n | ||
| if len(words) != needfracs + 5: | ||
| # printout("in lcp file '",filename,"':") | ||
| printout("in lcp file " + repr(filename) + ":") | ||
| printout("n=", n, ", need keywords 'M=' 'q=' 'd=' and n*n + n + n =", | ||
| needfracs, "fractions, got", len(words) - 5) | ||
| print("in lcp file " + repr(filename) + ":") | ||
| print("n=", n, ", need keywords 'M=' 'q=' 'd=' and n*n + n + n =", | ||
| needfracs, "fractions, got", len(words) - 5) | ||
| exit(1) | ||
| k = 2 # index in words | ||
| while k < len(words): | ||
|
|
@@ -106,8 +66,8 @@ def __init__(self, arg): | |
| self.d = utils.tovector(n, words, k) | ||
| k += n | ||
| else: | ||
| printout("in lcp file " + repr(filename) + ":") | ||
| printout("expected one of 'M=' 'q=' 'd=', got", repr(words[k])) | ||
| print("in lcp file " + repr(filename) + ":") | ||
| print("expected one of 'M=' 'q=' 'd=', got", repr(words[k])) | ||
| exit(1) | ||
| return | ||
|
|
||
|
|
@@ -139,6 +99,7 @@ def __str__(self): | |
| class tableau: | ||
| # filling the tableau from the LCP instance Mqd | ||
| def __init__(self, Mqd): | ||
| self.output_stream = None | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing the stream as state in the tableau is not great. However, this is more of a symptom of a deeper problem than a problem in itself - the |
||
| self.n = Mqd.n | ||
| n = self.n | ||
| self.scalefactor = [0] * (n + 2) # 0 for z0, n+1 for RHS | ||
|
|
@@ -266,15 +227,15 @@ def outsol(self): # string giving solution, after createsol() | |
|
|
||
| def assertbasic(self, v, info): # assert that v is basic | ||
| if self.bascobas[v] >= self.n: | ||
| printout(info, "Cobasic variable", self.vartoa(v), | ||
| "should be basic") | ||
| self.printout(info, "Cobasic variable", self.vartoa(v), | ||
| "should be basic") | ||
| exit(1) | ||
| return | ||
|
|
||
| def assertcobasic(self, v, info): # assert that v is cobasic | ||
| if self.bascobas[v] < self.n: | ||
| printout(info, "Cobasic variable", self.vartoa(v), | ||
| "should be cobasic") | ||
| self.printout(info, "Cobasic variable", self.vartoa(v), | ||
| "should be cobasic") | ||
| exit(1) | ||
| return | ||
|
|
||
|
|
@@ -283,15 +244,15 @@ def docupivot(self, leave, enter): # leave, enter in VARS | |
| self.assertcobasic(enter, "docupivot") | ||
| s = "leaving: " + self.vartoa(leave).ljust(5) | ||
| s += "entering: " + self.vartoa(enter) | ||
| printout(s) | ||
| self.printout(s) | ||
| return | ||
|
|
||
| def raytermination(self, enter): | ||
| printout("Ray termination when trying to enter", self.vartoa(enter)) | ||
| printout(self) | ||
| printout("Current basis not an LCP solution:") | ||
| self.printout("Ray termination when trying to enter", self.vartoa(enter)) | ||
| self.printout(self) | ||
| self.printout("Current basis not an LCP solution:") | ||
| self.createsol() | ||
| printout(self.outsol()) | ||
| self.printout(self.outsol()) | ||
| exit(1) | ||
|
|
||
| def testtablvars(self): # msg only if error, continue | ||
|
|
@@ -301,9 +262,9 @@ def testtablvars(self): # msg only if error, continue | |
| # injective suffices | ||
| for j in range(2 * n + 1): | ||
| if j == i: | ||
| printout("First problem for j=", j, ":") | ||
| self.printout("First problem for j=", j, ":") | ||
| # printout (f"{j=} {self.bascobas[j]=} {self.whichvar[j]=}") | ||
| printout( | ||
| self.printout( | ||
| f"j={j} self.bascobas[j]={self.bascobas[j]} " | ||
| f"self.whichvar[j]={self.whichvar[j]}" | ||
| ) | ||
|
|
@@ -312,7 +273,7 @@ def testtablvars(self): # msg only if error, continue | |
| def complement(self, v): # Z(i),W(i) are complements | ||
| n = self.n | ||
| if v == 0: | ||
| printout("Attempt to find complement of z0") | ||
| self.printout("Attempt to find complement of z0") | ||
| exit(1) | ||
| if v > n: | ||
| return v - n | ||
|
|
@@ -342,7 +303,7 @@ def outstatistics(self): | |
| stats.sprint(str(x / 10.0)) | ||
| else: | ||
| stats.sprint("-") | ||
| printout(stats) | ||
| self.printout(stats) | ||
|
|
||
| # returns leave,z0leave | ||
| # leave = leaving variable in VARS, given by lexmin row, | ||
|
|
@@ -377,7 +338,7 @@ def lexminvar(self, enter): | |
| j = 0 # going through j = 0..n | ||
| while len(leavecand) > 1: | ||
| if j > n: # impossible, perturbed RHS should have full rank | ||
| printout("lex-minratio test failed") | ||
| self.printout("lex-minratio test failed") | ||
| exit(1) | ||
| self.lextested[j] += 1 | ||
| self.lexcomparisons[j] += len(leavecand) | ||
|
|
@@ -468,8 +429,19 @@ def pivot(self, leave, enter): | |
|
|
||
| # end of pivot (leave, enter) | ||
|
|
||
| def runlemke(self, *, verbose=False, lexstats=False, z0=False, silent=False): | ||
| global filehandle | ||
| def printout(self, *s): | ||
| if self.output_stream is None: | ||
| return | ||
| print(*s, file=self.output_stream) | ||
|
|
||
| def runlemke( | ||
| self, | ||
| *, | ||
| verbose=False, | ||
| lexstats=False, | ||
| z0=False, | ||
| output_stream=None, | ||
| ): | ||
| # z0: printout value of z0 | ||
| # flags.maxcount = 0; | ||
| # flags.bdocupivot = 1; | ||
|
|
@@ -479,14 +451,14 @@ def runlemke(self, *, verbose=False, lexstats=False, z0=False, silent=False): | |
| # flags.binteract = 0; | ||
| # flags.blexstats = 0; | ||
|
|
||
| if silent: | ||
| filehandle = open(outfile, "w") # noqa: SIM115 | ||
| self.output_stream = output_stream | ||
|
|
||
| n = self.n | ||
| self.pivotcount = 1 | ||
| # check if d is ok - TBC | ||
| # if (flags.binitabl) | ||
| printout("After filltableau:") | ||
| printout(self) | ||
| self.printout("After filltableau:") | ||
| self.printout(self) | ||
|
|
||
| # z0 enters the basis to obtain lex-feasible solution | ||
| enter = 0 | ||
|
|
@@ -495,52 +467,75 @@ def runlemke(self, *, verbose=False, lexstats=False, z0=False, silent=False): | |
| self.negcol(n + 1) | ||
| # if (flags.binitabl) | ||
| if verbose: | ||
| printout("After negcol:") | ||
| printout(self) | ||
| self.printout("After negcol:") | ||
| self.printout(self) | ||
|
|
||
| while True: # main loop of complementary pivoting | ||
| self.testtablvars() | ||
| if z0: # printout progress of z0 | ||
| if self.bascobas[0] < n: # z0 is basic | ||
| printout( | ||
| self.printout( | ||
| "step,z0=", | ||
| self.pivotcount, self.A[self.bascobas[0]][n + 1] / self.determinant | ||
| ) | ||
| else: | ||
| printout("step,z0=", self.pivotcount, 0.0) | ||
| self.printout("step,z0=", self.pivotcount, 0.0) | ||
| # if (flags.bdocupivot) | ||
| self.docupivot(leave, enter) | ||
| self.pivot(leave, enter) | ||
| if z0leave: | ||
| if z0: | ||
| printout("step,z0=", self.pivotcount + 1, 0.0) | ||
| self.printout("step,z0=", self.pivotcount + 1, 0.0) | ||
| break | ||
| if verbose: | ||
| printout(self) | ||
| self.printout(self) | ||
| enter = self.complement(leave) | ||
| leave, z0leave = self.lexminvar(enter) | ||
| self.pivotcount += 1 | ||
|
|
||
| # if (flags.binitabl) | ||
| printout("Final tableau:") | ||
| printout(self) | ||
| self.printout("Final tableau:") | ||
| self.printout(self) | ||
| # if (flags.boutsol) | ||
| self.createsol() | ||
| printout(self.outsol()) | ||
| self.printout(self.outsol()) | ||
| if lexstats: | ||
| self.outstatistics() | ||
| # end of class tableau | ||
|
|
||
|
|
||
| def main(): | ||
| processArguments() | ||
| printout(f"verbose={verbose} lcpfilename={lcpfilename} silent={silent} z0={z0}") | ||
| @click.command( | ||
| context_settings={"help_option_names": ["-?", "-h", "--help"]}, | ||
| ) | ||
| @click.option( | ||
| "-v", | ||
| "--verbose", | ||
| is_flag=True, | ||
| help="Printout intermediate tableaus", | ||
| ) | ||
| @click.option( | ||
| "--z0", | ||
| is_flag=True, | ||
| help="Show value of z0 at each step", | ||
| ) | ||
| @click.argument( | ||
| "lcpfilename", | ||
| type=click.Path(exists=True, readable=True, file_okay=True, dir_okay=False), | ||
| ) | ||
|
Comment on lines
+510
to
+524
|
||
| def main(verbose, z0, lcpfilename): | ||
| """ | ||
| Tool for solving linear complementarity problems using Lemke's algorithm. | ||
|
|
||
| LCPFILENAME is the path to the input file. | ||
| """ | ||
|
|
||
| print(f"verbose={verbose} lcpfilename={lcpfilename} z0={z0}") | ||
| # printout (f"{verbose}= {lcpfilename}= {silent}= {z0}=") | ||
| m = lcp(lcpfilename) | ||
| printout(m) | ||
| printout("==================================") | ||
| print(m) | ||
| print("==================================") | ||
| tabl = tableau(m) | ||
| tabl.runlemke(verbose=verbose, z0=z0, silent=silent) | ||
| tabl.runlemke(verbose=verbose, z0=z0, output_stream=sys.stdout) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upper bounds on packages should only be used in the event of known incompatibilities, especially when working with mature packages (like
numpy,matplotlib,clickall are). Upper bounds can cause version conflicts when installing alongside other packages in the same environment.