-
Notifications
You must be signed in to change notification settings - Fork 30
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
Possible 85-95% reduction in stage 2 reweighting time using drop-in-replacement solver in solver.jl #381
Comments
Thanks for doing the research on this, @donboyd5. I'm also annoyed with how long it takes the solver to finish running. I'll test out dropping this in later today! |
@donboyd5, when I try to test your suggested changes I get this error:
Did you run into this at any point when you were working with Tulip? I haven't been able to find a solution for it yet. |
@donboyd5 can you also tell me what Julia version you're using? I've been having trouble getting things running and I think it might have to do with the Julia/package versions. |
@andersonfrailey, thanks for checking this out. I have not run into the specific error you show. As for versions, first I used Julia 1.4.1 and it worked fine; today I upgraded to 1.6.1 and both Tulip and Cbc work fine in stand-alone Julia programs. I have not yet gone back to make sure that everything works in the I'll make sure everything works in the taxdata context and after doing that I'll post the solver.jl code. But I think the example below should help determine where the problem lies.
|
@andersonfrailey the code below is a full drop-in replacement for solver.jl in the cps_stage2 folder. On my computer, with Julia 1.6.1, it works without trouble. I am 99.9% sure same is true of Julia 1.4.1 but I no longer have that installed to check. Depending on whether you set solver="Tulip" or solver="Cbc" it will use the corresponding solver. You can set Tulip iterations with the variable Tulip_max_iter (I have not figured out how to set max iterations for Cbc; left alone it needs about a quarter million iterations). I did try two other solvers (Clp and CSDP) but neither is as good as Tulip. I read about several other JuMP-available solvers but the ones I look at require Matlab or have a non-open-source license. I may look a the few other options over the next week or two but I am going to guess that Tulip will be as good as it gets when you are using a linear programming approach. Further speedups are possible with other approaches but its probably not worth the effort because the Tulip speed is pretty good and other approaches would require more-substantial code changes.
|
@andersonfrailey I think if you were to use Tulip and stop it before optimality -- which I would recommend given how close the 500-iteration and even 100-iteration runs come to optimality as Cbc defines it -- I think you would want to put in a check to be sure that all the constraints have been satisfied within a reasonable tolerance. That has always been the case in the dozens of solves I have looked at -- usually the constraints seem to be satisfied after a few tens of iterations -- but I'd definitely put in a check. |
Thanks, @donboyd5! I was able to get things going with a bit of work. It was just an issue with the |
I've been running the current solver for ~24 hours to create new PUF weights and it still isn't finished so I'd like to say that I'm 100% in favor of moving to a different solver. I don't remember it being this slow when we initially switched... |
Agree wholeheartedly. If you look at old puf stage2 results in the repo (I don't know how old), done with Clp, you'll see that they regularly took about 30k iterations (see screenshot from the repo of results for 2012). By contrast, the current solver, Cbc (which I thought called Clp but I am not sure), regularly requires a quarter-million iterations. While 30k was awful, 250k is simply unbearable. As I mentioned, the CPS took 14 hours on my reasonably fast machine. I have since used Tulip some more and find that 500 iterations is sufficient even though it does not achieve optimality. Although tinkering might get us there, it hits diminishing returns far sooner than 500 iterations and 100 is perfect for testing (about 1.5 minutes per year). As noted, I'd put in a check to be sure the solution is "good enough" at 500 iterations. I can help with that if you want. |
I have a version that now solves a single puf year (2012 - not tested on other years) reaching optimality in about 25 seconds in 46 iterations, satisfies the constraints, and gets a better objective function value than the current taxdata gets after an hour with 250 thousand iterations (approx). It would be easy to make a simple drop-in replacement for solver.jl that does this. To accomplish this, I made the following 3 changes to solver.jl, while keeping the overall structure the same:
Tail end of output shown below:
Happy to help you implement this if you want. Don |
Copied below is drop-in-replacement code for puf stage2 solver.jl. It implements all 3 changes noted above. I don't think I left any residue from experiments in the file (three residue lines are commented out) so you should be able to simply replace puf stage2 solver.jl with this code and try it out. I made minor changes to scaling relative to what I described above: the scaling factor for each constraint is now chosen so that, when multiplied elementwise by the "A" matrices, the sum of absolute values in the row is the number of records divided by 1000 (252.8k records / 1000, or about 252.8). (Each A matrix has 27 rows (1 per constraint) and 252.8k columns (1 per taxpayer/nonfiler)). The general idea is to keep numbers from getting too large and causing numerical difficulty. I'm sure this is not optimal scaling but it is a LOT better than no scaling. (I don't know a cookbook approach to scaling the problem optimally.) It prints a short report after each year showing:
I ran it on all years of the puf:
I copy below the final part of the summary for 2012, in case you want to run it on a single year and see if you get the same results. You should get the same values for everything but runtime, I think. You can see that:
|
I don't know if others have found reweighting to be as slow as I have, but stage 2 reweighing for CPS for 17 years took more than 14 hours on an older but still reasonably fast CPU. That's more time than I can tie a computer up for ordinarily.
As some of us have discussed, this really shouldn't take more than half a minute or so a year with an NLP solver and slight problem restructuring.
But in this case I was looking for a pure LP solution that required no restructuring.
After looking over the list of available JuMP LP solvers, I decided to investigate Tulip (here and here).
After installing, it can be dropped into solver.jl with just the following code changes:
Here are summary results for 2014. As you can see Cbc takes a quarter-million iterations. For Tulip, I raised the default limit on number of iterations from 100 to 500 so that the solution values are almost identical to those from Cbc for 2014:
Objective function:
Cbc (taxdata): 34,003.997
Tulip (500 iter) 34,004.8231
Tulip (100 iter) 34,027.7230
Iterations:
Cbc (taxdata): 253,373
Tulip (500 iter) 500
Tulip (100 iter) 100
Solution time (minutes):
Cbc (taxdata): 48.8
Tulip (500 iter) 6.4
Tulip (100 iter) 1.3
Quantiles of the key result (ratio of new weight to old weight), (0, .1, .25, .5, .75, .9, 1):
Cbc: [0.3, 1. , 1. , 1. , 1. , 1.7]
Tulip (500 iter) [0.3 0.99999999, 1. , 1. 1. , 1.7]
Tulip (100 iter) [0.30000021, 0.99998908, 0.99999819, 1. , 1.00000274, 1.69999996]
Correlations of the key result (ratio of new weight to old weight):
Cbc compared to Tulip with 500 iterations:
array([[1. , 0.9987413],
[0.9987413, 1. ]])
Cbc compared to Tulip with 100 iterations:
array([[1. , 0.99372129],
[0.99372129, 1. ]])
I did this for 2015, also, and results were similar. I have not looked at other years.
Tulip does not use a lot of memory, so that will not be a consideration on most machines.
Conclusions
It seems pretty clear to me that you can have a massive speedup with a near-drop-in-replacement and it might be worth considering. Tulip did not reach optimality at 500 iterations but the objective function was only 0.002% higher than that of Cbc. You might be able to get Tulip closer to the Cbc optimal result by varying one or more options, but it doesn't seem worth a lot of effort. Personally, I'd consider the result from 100 iterations from Tulip great for testing purposes and would only run more iterations for creation of a production file.
NOTE: When I say Tulip did not reach optimality, I mean that it did not think it had reached the minimum objective function -- the sum of (absolute differences between the ratio of new weight to old weight, minus 1). But more important, it satisfied the constraints very early on, within its tolerances -- that is, constraints for wages by income range, etc., all were met (and FAR sooner than Cbc met them).
You might be tempted to run Cbc for fewer iterations but looking at the output log, its results seem far from acceptable even at 200,000 iterations. It is not until it gets close to 250,000 iterations that the solution looks really good. (Obviously this varies from year to year, but the general pattern remains.)
I am a newcomer to Julia, JuMP, and Tulip. There may well be ways to speed it up further, but it is pretty good out of the box.
Obviously I would not recommend it as a replacement for Cbc without further testing. Sometimes one solver finds a particular problem harder than another solver and it's always possible that it will not be robust enough. Still based on my checking so far, it looks like a much better solver for taxdata purposes than Cbc and I'd recommend testing and probably deploying it. I plan to use it in my use of taxdata.
The text was updated successfully, but these errors were encountered: