I’m taking the Coursera course Discrete Optimization. It’s fun and challenging and I doubt I’ll have time to do very well, but I really enjoy this sort of thing. The programming assignments are fun, the lectures are hilarious. (The professor dressed as Indiana Jones and pretended to be literally trying to solve the Knapsack Problem because the temple was collapsing, for example.)
The submission process for programming assignments is pretty straightforward. Basically, you get a file called
solver.py, which you edit so that it returns the answer you want, and then you get a file called
submit.py, which calls
solver.py and sends the answer up to Coursera for grading. Many of the students will either write their solution in place in
solver.py or will call another python file from that one. But the course specifically permits you to write in another language, and then just have
solver.py invoke whatever else you wrote, by calling and executable and reading its output, for example.
Now, I’m trying to learn Rails, and making Rub gems, and Vagrant. I hope you see where this is going. Maybe there will be a prize for most CPU instructions executed in preparation for attempting to solve the homework question.
Here’s my setup:
solver.pyinvokes a shell command that spins up a Vagrant box.
- The Vagrantfile, if it’s called in this way, will immediately spin up a rails server on localhost.
- Python will call localhost over http to get the answer to the assignment.
This makes me very happy. I want to use a Ruby web server, so in order to do my calculations, Python will ask Vagrant to spin up a Rails server and then ask the rails server what the answer is, and then submit that.
Please keep in mind, I do not recommend doing this as an efficient way of passing the course. I am quite certain that, in addition to having to do the very real programming work that’s part of this course, I will also have to debug stupid issues around serializing and deserializing the input and output for these problems. I am doing this because I have two goals:
- Get better at computer science in general.
- Learn to build non-trivial Rails apps.
I think this course will be a good opportunity to learn separation of concerns in Rails. Basically, I think the python script will send input from the course over to my rails app. The rails controller will decide which gem to pass the input off to. That way, each algorithm I attempt to design for the course will be its own gem that tries to solve one problem one way.
The first thing that I want to do is be able to tell Vagrant that I want Rails to start immediately; ordinarily
vagrant up boots the machine and runs a provisioning script. Now I want the provisioning script to spin up Rails, but only sometimes; I don’t want my Vagrantfile to always start the server. I found one quesion on StackOverflow that makes it seem much easier to just use an environment variable than to add your own argument parsing on top of whatever Vagrant is already doing. So I added this to my Vagrantfile:
if(ENV['serve_now'] == 'true') config.vm.provision "shell", privileged: false, inline: <<-SCRIPT (cd /vagrant/solver && bundle exec puma -C config/puma.rb) SCRIPT end
Now, using the temporary-environment-variable thing in Linux shells, I can just add this to
os.system(""" (cd /path/to/my/Vagrantfile && serve_now=true vagrant up dev --provision --provider=virtualbox) """)
And it almost works! The problem is that
vagrant up returns control to the host OS after all the shell commands return. But
bundle exec puma -C config/puma.rg happily prints out a message that it is now serving on port so-and-so, and I can press Ctrl-C to exit. Of course, this is running inside an SSH session inside a Python script, so who knows where my interrupt key will land. To get out of this, I had to open a different SSH session into the same thing and kill processes until I could shut it down. It turns out what I was missing is this, in
config/puma.rg in my rails app:
daemonize true. This means that Puma will serve as a background process, rather than hogging that shell session forever.
And then, it works! Without giving away anything about how to do this part of the course,
solver.py has this line:
result = urllib.request.urlopen("http://localhost:3000/solver/solve").read()
And there we go! My python solver is calling a Rails app to ask for the answer, and I can keep working on the course in Ruby instead of Python. (Yes, I know that I could have written
os.system("ruby solver.rb") instead, but then I wouldn’t really be learning Rails or Vagrant.)
Anyway, that was fun, and now I have some discrete optimization gems to try to write.
Till next time, happy learning!