tag:blogger.com,1999:blog-147699352018-03-07T07:34:47.478-08:00Questions Nobody Asked..Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.comBlogger107125tag:blogger.com,1999:blog-14769935.post-48645881537132123592017-04-02T11:22:00.000-07:002017-04-02T11:25:45.782-07:00Testing Python Unicode Handling the Lazy Way<b>Background</b><br /><br />At work, we've been working on a <a href="http://arcapix.com/gpfsapi/">Python API</a> for IBM's <a href="http://www-03.ibm.com/systems/uk/storage/spectrum/scale/">Spectrum Scale</a> (GPFS) filesystem.<br /><br />The API is well tested, with good coverage. But one thing that's caught us out a couple of times is unicode.<br /><br />For those not familiar, Python 2.7 has both byte strings and unicode strings. In most cases, the two are interchangeable. But there are some quirks, which can lead to bugs if not handled correctly.<br /><br />Now it's usually safe to assume that users will work with byte strings - mostly because when you create a string, that is the default format. And internally, we always use byte strings.<br /><br />But this being an API, it gets used by other people. And what I've come to learn is users do all sorts of weird things. The amount of bugs we've had from frankly bizarre filenames... (why would anyone put a newline character it a file name?!) <br /><br />And users aside, unicode can also come from interfacing with other code - for example, the <span style="font-family: "courier new" , "courier" , monospace;">json.loads</span> method.<br /><br />All of which is to say, realistically, it's best not to ignore unicode.<br /><br /><br /><b>Unittesting</b><br /><br />Listen, I understand the value and importance of <a href="https://en.wikipedia.org/wiki/Unit_testing">unittesting</a>. I just find it so dull compared to writing 'proper' code. And frustrating! In my experience, most of the errors and fails raised by unittests come from the tests themselves, rather than the code they're supported to be testing. (Maybe that's on me).<br /><br />So yeah. The thought of having to write a whole bunch of new unittests - most of which were going to be more or less exact copies of the tests that already existed - didn't appeal to me.<br /><br />Instead, I wondered if maybe there was a lazier way of doing it.<br /><br />The easiest way to do that was to take advantage of the tests that we'd already written. All we needed was a way to run those tests with some mechanism for convert any strings passed to API function calls to unicode.<br /><br /><br /><b>Set Trace</b><br /><br />One thing I particularly like about Python is the ability to monkey patch things - playing with existing code on the fly, without having to change the actual source code.<br /><br />That's why the <span style="font-family: "courier new" , "courier" , monospace;">sys.settrace</span> function is one of my favourites.<br /><br />I came across set trace when I was looking at ways to do logging in the API. We wanted to be able to log function calls, functions returns, etc. The problem was, we didn't really want to add individual logging statements to each and every function. Aside from the effort, it'd really clutter the code.<br /><br />After considering a couple other options (decorators, metaclasses), I came across settrace.<br /><br /><a href="https://docs.python.org/2/library/sys.html#sys.settrace">settrace</a> isn't well explained in the official docs. You can find a better explanation <a href="https://pymotw.com/2/sys/tracing.html#sys-tracing">here</a>.<br /><br />settrace allows you to set a trace function - a function which will be called for every line of code run, for every function call and return, for every exception - perfect!<br /><br />A trace function receives 3 arguments - <span style="font-family: "courier new" , "courier" , monospace;">event</span>, <span style="font-family: "courier new" , "courier" , monospace;">frame</span>, and <span style="font-family: "courier new" , "courier" , monospace;">args</span>.<br /><br /><span style="font-family: "courier new" , "courier" , monospace;">event</span> is a string indicating what the code is doing (line, call, return).<br /><br /><span style="font-family: "courier new" , "courier" , monospace;">args</span> is some arguments, which vary depending on the event - for example, if <span style="font-family: "courier new" , "courier" , monospace;">event</span> is <span style="font-family: "courier new" , "courier" , monospace;">'return'</span>, then <span style="font-family: "courier new" , "courier" , monospace;">args</span> will hold the function return value.<br /><br />And <span style="font-family: "courier new" , "courier" , monospace;">frame</span> is a <a href="https://docs.python.org/2/library/inspect.html">frame</a> object. This is where we get most of our information for reporting.<br /><br />The frame object holds some interesting stuff - the <span style="font-family: "courier new" , "courier" , monospace;">f_locals</span> attribute holds the frame locals<span style="font-family: "courier new" , "courier" , monospace;">,</span> and in the case of a call event, these locals are any variables that have been passed into the function.<br /><br />There's also <span style="font-family: "courier new" , "courier" , monospace;">f_code</span> - the code object for the function being called. And from that we can get things like <span style="font-family: "courier new" , "courier" , monospace;">f_code.co_name</span> - the name of the function being called/returned from.<br /><br />So as a simple example we might have<br /><br /><pre class="brush:py">import sys<br /><br />def trace(frame, event, args):<br /> if event == "call":<br /> print frame.f_code.co_name, "called with", frame.f_locals<br /> elif event == "return":<br /> print frame.f_code.co_name, "returned", args<br /> return trace<br /><br />sys.settrace(trace)<br /></pre><br />I ended up using <span style="font-family: "courier new" , "courier" , monospace;">settrace</span> to write an '<a href="https://en.wikipedia.org/wiki/Strace">strace</a>' style script, which can be used to trace API function calls for a given scrip<span style="font-family: inherit;">t or piece of code</span>. Which is pretty cool.<br /><br /><br /><b>The Solution</b><br /><br />So how does this apply to the unicode problem?<br /><br />As mentioned above, we can get the parameters passed to the function from <span style="font-family: "courier new" , "courier" , monospace;">frame.f_locals</span>. And because <span style="font-family: "courier new" , "courier" , monospace;">f_locals</span> is a dict, it's mutable. That means that we can change it's values, and those changes will persist when the function being traced continues executing.<br /><br />This is how this solution works - we convert any strings in <span style="font-family: "courier new" , "courier" , monospace;">f_locals</span> to unicode. The code being 'traced' then behaves as if its functions had been passed unicode to begin with.<br /><br />While we're at it, we have to make sure we also convert any strings in lists, tuples, dicts - in particular because <span style="font-family: "courier new" , "courier" , monospace;">*args</span> and <span style="font-family: "courier new" , "courier" , monospace;">**kwargs</span> are ultimately just a tuple and a dict. <br /><br /><span style="font-family: "courier new" , "courier" , monospace;">H</span>ere's the complete solution<br /><br /><pre class="brush:py">"""Unittest wrapper, which converts strings to unicode.<br /><br />Check that your code can handle unicode input<br />without having to write new unittests.<br /><br />Usage is identical to unittest:<br /><br />$ python -m unicodetest tests.unit.test_whatever<br />"""<br />import atexit<br />import sys<br /><br /># mimic the behaviour of unittest/__main__.py<br />from unittest.main import main, TestProgram, USAGE_AS_MAIN<br />TestProgram.USAGE = USAGE_AS_MAIN<br /><br /><br />def unicodify(value):<br /> """Convert strings to unicode.<br /><br /> If value is a collection, its members<br /> will be recursively unicodified.<br /> """<br /> if isinstance(value, str):<br /> return unicode(value)<br /> if type(value) is dict:<br /> return {k: unicodify(v) for k, v in value.iteritems()}<br /> if type(value) in (list, tuple, set):<br /> return type(value)(unicodify(v) for v in value)<br /> return value<br /><br /><br />def unicoder(frame, event, args):<br /> """For all function calls, convert any string args to unicode."""<br /> if event == "call":<br /> for k, v in frame.f_locals.iteritems():<br /> frame.f_locals[k] = unicodify(v)<br /> return unicoder<br /><br /><br />if __name__ == '__main__':<br /> # make sure unicoder is disabled at exit<br /> atexit.register(lambda: sys.settrace(None))<br /><br /> # activate unicoder<br /> sys.settrace(unicoder)<br /><br /> # run unittests<br /> # cli args are passed thru to unittest<br /> # so the usage is identical<br /> sys.argv[0] = "python -m unicodetest"<br /> main(module=None)<br /></pre><br /><br />I decided mimic the <span style="font-family: "courier new" , "courier" , monospace;">unicode/__main__.py</span> script, so that it works as a drop-in replacement for the Python <a href="https://docs.python.org/2/library/unittest.html">unittest</a> module - e.g.<br /><br /><span style="font-family: "courier new" , "courier" , monospace;">$ python -m unicodetest discover -v tests/</span><br /><br />This sets the trace to the unicoder function, then calls the usual unittest method to run whatever tests we have pre-written.<br /><br /><br /><b>Dummy Test</b><br /><br /><pre class="brush:py">$ cat test_dummy.py<br /><br />from unittest import TestCase<br /><br />def dummy(value):<br /> return value<br /><br />class Test_Type(TestCase):<br /><br /> def test_string_type(self):<br /> self.assertIsInstance(dummy('foo'), unicode)<br /><br />$ python -m unittest test_dummy<br /><br />test_string_type (testtype.Test_Type) ... FAIL<br /><br />======================================================================<br />FAIL: test_string_type (testtype.Test_Type)<br />----------------------------------------------------------------------<br />Traceback (most recent call last):<br /> File "testtype.py", line 13, in test_string_type<br /> self.assertIsInstance(dummy('foo'), unicode)<br />AssertionError: 'foo' is not an instance of <type 'unicode'><br /><br />----------------------------------------------------------------------<br />Ran 1 test in 0.001s<br /><br />FAILED (failures=1)<br /><br />$ python -m unicodetest test_dummy<br /><br />test_string_type (testtype.Test_Type) ... ok<br /><br />----------------------------------------------------------------------<br />Ran 1 test in 0.002s<br /><br />OK<br /></pre><br /><br /><b>Comments and Caveats</b><br /><br />In <span style="font-family: "courier new" , "courier" , monospace;">unicodify</span> I've used <span style="font-family: "courier new" , "courier" , monospace;"> </span><br /><br /><span style="font-family: "courier new" , "courier" , monospace;">if type(value) is dict</span><br /><br />PEP8 <a href="https://www.python.org/dev/peps/pep-0008/#programming-recommendations">recommends</a> instead using<br /><br /><span style="font-family: "courier new" , "courier" , monospace;">if isinstance(value, dict)</span><br /><br />But this caused issues for us. In the API, we use <span style="font-family: "courier new" , "courier" , monospace;">OrderedDict</span> as one of the base classes for collection objects. But their init functions doesn't have the same function signature as a dict (ordered or otherwise).<br /><br />So using <span style="font-family: "courier new" , "courier" , monospace;">isinstance</span> causes things to break. But that's fine - in the case of our collection objects, the members don't need unicodifying anyway. This will, however, miss actual <span style="font-family: "courier new" , "courier" , monospace;">OrderedDict</span>s, so you may wish to change the code accordingly. <br /><br /><br />When I first tried the code, I kept getting the following error message<br /><br /><span style="font-family: "courier new" , "courier" , monospace;">Exception TypeError: "'NoneType' object is not callable" in <function _remove at 0x7f4a9aae36e0> ignored</span><br /><br />It wasn't fatal - the code worked in spite of it - but it was a little off-putting. With a little Googling, I found someone with <a href="https://github.com/ionelmc/python-hunter/issues/7">the same problem</a>, and a <a href="https://github.com/ionelmc/python-hunter/commit/207e8c63ee75cbe4c83c97f32c17c6a67b62a55e#diff-c10b24d69ebd64b6c4e526e9372b0ddaR125">corresponding solution</a>.<br /><br />Basically, the issue was with the trace function not being disabled properly on exit. That's what the <a href="https://docs.python.org/2/library/atexit.html"><span style="font-family: "courier new" , "courier" , monospace;">atexit</span></a> line is for.<br /><br /><br />If the code you're testing is fairly simple, then you can use the code above as is. If it's a bit more complex, you'll probably find that converting strings to unicode in ALL functions causes problems in code outside of your control - in builtin modules, 3rd party modules.<br /><br />This was the case for our API - the conversion seemed to upset regex (or something).<br /><br />In this case, we need to make a slight tweak to make it only affect calls to functions from a particular module (the one we're trying to test).<br /><br />In this case, we use the <a href="https://docs.python.org/2/library/inspect.html#inspect.getmodule"><span style="font-family: "courier new" , "courier" , monospace;">insp<span style="font-family: "courier new" , "courier" , monospace;">e</span>ct.getmodule</span></a> method with the frame's <span style="font-family: "courier new" , "courier" , monospace;">f_code</span> member. That lets us identify what module the function being called came from, and apply unicoding conversion (or not) accordingly.<br /><br /><pre class="brush:py">module = inspect.getmodule(frame.f_code)<br />if module and module.__name__.startswith('mymodule'):<br /> # etc.<br /></pre><br />Now, I'm not a fan of hard-coding a module name into the script. Ideally, I'd probably add a command line flag to specify the module of interest. But parsing that would make the code more complicated, and would break the 'drop-in replacement' nature of the script. So this is left as an exercise for the reader. <br /><br /><br /><b>Conclusion</b><br /><br />So<span style="font-family: "courier new" , "courier" , monospace;">,</span> rather than writing a whole bunch of new tests, I got away with writing only ~50 lines of code. And I didn't even have to change any of the existing code.<br /><br />Hurray for laziness.<br /><br /><br />Oatzy.<br /><br /><br />[inb4 use <a href="https://docs.python.org/3/howto/unicode.html#python-s-unicode-support">Python 3</a>]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com1tag:blogger.com,1999:blog-14769935.post-41314845743233475682016-07-31T10:09:00.000-07:002016-07-31T11:44:07.301-07:00Barber QueueTime was when I needed a hair cut, I'd go at noon on a weekday, when the barber's is typically empty. One of the perks of being unemployed.<br /><br />But these days I have to get my haircuts on Saturdays, before noon. Which is bad enough in itself - ideally I'd never see Saturday mornings at all. And to make matters worse, Saturday morning is also when the barbers is at its busiest.<br /><br />Hence, I found myself sat in the waiter area of a barbershop for the best part of an hour. But this got me thinking about how queuing works at a barbers.<br /><br /><br /><br /><b>First In Who's Next?</b><br /><br />At its core, the barber's queue is just a <a href="https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)">first-in first-out</a> (FIFO) queue. But it has two interesting features:<br /><br /><br />1) <i>The queue 'structure' is unordered</i><br /><br />In general the queue 'structure' will be a waiting area with a bunch of seats. When someone new joins the queue, they're free to sit wherever. In fact, odds are, they'll pick a seat in a similar way to <a href="https://blog.xkcd.com/2009/09/02/urinal-protocol-vulnerability/">how men choose urinals</a> - attempting to maximise personal space. <br /><br />But the key point is, if someone were to just look at the queue, they wouldn't be able to tell who was next.<br /><br /><br />2) <i>Each member of the queue knows whether or not they're next</i><br /><br />Each member of the queue probably doesn't know <i>who</i> exactly is next, but they do know (with reasonable certainty) whether or not it's them.<br /><br />The way this works is relatively simple - when you join the queue, you're aware of who was there when you arrived (and of anyone who arrives after you). So when all the people who were there ahead of you have gone, you know that you're next.<br /><br /><br /><br /><b>O(M G)</b><br /><br />Okay, lets break out some Python (2.7)<br /><br />Just for fun, let's say that the capacity of the queue is fixed - i.e. the waiting area has a fixed number of seats (though in practice, people are free to stand, as I was forced to).<br /><br /><pre class="brush:py">class BarberQueue(object):<br /><br /> def __init__(self, capacity):<br /> self._capacity = capacity<br /> self._queue = [None]*capacity<br /> self._length = 0<br /><br /> def __len__(self):<br /> return self._length<br /><br /> def __str__(self):<br /> return ', '.join(str(i) if i is not None else '_'<br /> for i in self._queue)<br /><br /> def push(self, obj):<br /> pass<br /><br /> def pop(self):<br /> pass<br /><br /></pre>So each member of the queue has some awareness of who is ahead of them. But they don't need to know specifically who's who, they just need to keep track of how many are remaining. And in fact, that remaining count is exactly equivalent to the member's position in the queue.<br /><br /><pre class="brush:py">class Member(object):<br /><br /> def __init__(self, obj, position=0):<br /> self.value = obj<br /> self._position = position<br /><br /> def __str__(self):<br /> return "%s (%s)" % (self.value, self._position)<br /><br /> def is_next(self):<br /> return self._position == 0<br /><br /> def move_up(self):<br /> self._position -= 1<br /><br /></pre>So, going back to the push and pop methods<br /><br /><pre class="brush:py">def push(self, obj):<br /><br /> if self._length == self._capacity:<br /> raise Exception("Queue is full! Please come back later.")<br /><br /> for i, m in enumerate(self._queue):<br /> if m is None:<br /> self._queue[i] = Member(obj, self._length)<br /> self._length += 1<br /> return<br /></pre><br />Here, we're picking a 'seat' by iterate over the queue looking for the first empty slot (with a value of None). We could implement any seat picking strategy we fancy, this is just the easiest.<br /><br />Once we find an empty seat, we create a new 'Member' object for the item, with position set to the current length of the queue, then increment the queue length. Also, if the queue has no empty slots, we raise an exception.<br /><br /><pre class="brush:py">def pop(self):<br /> if self._length == 0:<br /> raise Exception("The queue is empty")<br /> for i, m in enumerate(self._queue):<br /> if m is not None:<br /> if m.is_next():<br /> value = m.value<br /> self._queue[i] = None<br /> self._length -= 1<br /> else:<br /> m.move_up()<br /> return value<br /><br /></pre>Here we iterate over the queue looking for the member who is 'next' (has position 0). While we're looking for the next person, we also de-increment the positions of the other members.<br /><br />Of course, this isn't how things work in practice. The barber doesn't go to each person and say "<i>are you next?</i>", "<i>how about you?</i>". They simply say "<i>who's next?</i>", and the person who believes they are next steps forward. Though arguably, that's just equivalent to asking every member concurrently. But let's not complicate things.<br /><br /><pre class="brush:py">>>> b = BarberQueue(3)<br />>>> for i in xrange(3):<br /> b.push(i)<br /> <br />>>> print b<br />0 (0), 1 (1), 2 (2)<br />>>> b.push(4)<br /><br />Traceback (most recent call last):<br /> File "<pyshell>", line 1, in <module><br /> b.push(4)<br /> File "<pyshell>", line 36, in push<br /> raise Exception("Queue is full! Please come back later")<br />Exception: Queue is full! Please come back later<br />>>> b.pop()<br />0<br />>>> print b<br />_, 1 (0), 2 (1)<br />>>> b.push(4)<br />>>> print b<br />4 (2), 1 (0), 2 (1)<br />>>> for _ in xrange(4):<br /> b.pop()<br /> <br />1<br />2<br />4<br /><br />Traceback (most recent call last):<br /> File "<pyshell>", line 2, in <module><br /> b.pop()<br /> File "<pyshell>", line 46, in pop<br /> raise Exception("Empty queue!")<br />Exception: Empty queue!<br />>>> print b<br />_, _, _<br /></pre><br />So there we have it. Of course, this type of queue isn't really useful from a programming perspective.<br /><br />All of insertion, deletion, and lookup are worst-case \(O(N)\), where N is the queue's capacity (<i>not</i> the number of people in the queue). Which is pretty much worse than all other types of queue.<br /><br /><br /><br /><b>Why do items keep disappearing from my queue?</b><br /><br />Okay, let's move away from computer-sciencey queues. There are certain behaviours in real-world queues that don't apply or wouldn't make sense to programmatic queues.<br /><br />For one, as I alluded to earlier, the capacity of the queue is not enforced - there's room for overflow, even if it means people have to stand.<br /><br />But on the other hand, when a place does get that full, people are less likely to stick around.<br /><br />In particular, we have two situations:<br /><br /><br />1) There's a non-zero <i>probability that a person will not stick around if the queue is full</i> or close to full. This probability will tend to be related to the length of the queue when that person arrives<br /><br />\[p(not join) \sim f(capacity, length)\]<br /><br />For example,<br /><br />\[p(not join) = A\left(\frac{length}{capacity}\right) - C\]<br /><br />where A and C are some constants relating to how likely the person is to stick around if the queue is 'full', and at what point they consider the place to be 'too full'.<br /><br /><br />2) There's a non-zero <i>probability that a person already in the queue will leave</i> before they're served. This probability will typically depend on how long the person has been waiting, and how many people are still ahead of them<br /><br />\[p(leave) \sim g(wait, position)\]<br /><br />It's interesting because as time passes, wait increases, but position decreases. So how the probability evolves depends on how those factors balance against one another.<br /><br />In particular, the probability evolution will likely depend on how each particular person responds to the <a href="https://youarenotsosmart.com/2011/03/25/the-sunk-cost-fallacy/">sunk-cost fallacy</a> - i.e. are they the sort to think "<i>well, I've waited this long, I might as well see it through to the end</i>", or do they think "<i>this is taking too long, I've got better things to do with my time</i>"?<br /><br />For the sake of arguing, lets go with an <a href="https://en.wikipedia.org/wiki/Exponential_function">exponential function</a> for the general form.<br /><br />For a sunk-cost person we might have<br /><br />\[p(leave) = B \cdot \exp\left(-d\cdot \frac{wait}{position}\right)\]<br /><br />This is a function where p goes to zero as position goes to zero or wait goes to infinity (B and d are some arbitrary constants). <br /><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-yDZ24fuerl4/V50Nz2o5pkI/AAAAAAAABso/6XOKAgsfUnMnrAZnDgLzj3b07cZOsiCKQCLcB/s1600/p-leave-sunkcost.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://2.bp.blogspot.com/-yDZ24fuerl4/V50Nz2o5pkI/AAAAAAAABso/6XOKAgsfUnMnrAZnDgLzj3b07cZOsiCKQCLcB/s1600/p-leave-sunkcost.png" /></a></div><br />Whereas for a non-sunk-cost person we might have<br /><br />\[p(leave) = B \cdot \exp\left(-\frac{d}{wait \cdot position}\right)\]<br /><br />This is a function where p goes to zero as position goes to zero, but goes to one as wait goes to infinity.<br /><br />This gives us an interesting graph<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://3.bp.blogspot.com/-kmy7xE_sq1U/V50N6yjfexI/AAAAAAAABss/7tkHLXjrf8IUvpjCBJehGbxj08DJJDDyACLcB/s1600/p-leave-no-sunkcost.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://3.bp.blogspot.com/-kmy7xE_sq1U/V50N6yjfexI/AAAAAAAABss/7tkHLXjrf8IUvpjCBJehGbxj08DJJDDyACLcB/s1600/p-leave-no-sunkcost.png" /></a></div><br />Because 'position' is discrete you get this nice step function, with intervals of the probability steadily rising, then suddenly dropping. We can also see that there's a point at which probability of leaving is maximum, around the time you're in the middle of the queue. Which seems plausible.<br /><br /><br />We also have situations where a person will leave and come back later. But since, when they come back, they have to join the back of the queue, they're mathematically indistinguishable from a someone arriving for the first time.<br /><br /><br />One other complicating situation is people in groups. For example, if there's a parent and child ahead of you in the queue, the child is getting their hair cut by one member of staff, the parent is waiting; another member of staff asks 'who's next?' - is it you or the parent?<br /><br />Situations like these add uncertainty into a member's queue position, and by extension their knowledge of whether they're next.<br /><br />In that situation, we might wait to see if anyone else steps up, and if not we can assume it's our turn.<br /><br />So we have \(p(next)\), which is a <a href="https://en.wikipedia.org/wiki/Bayes_theorem">Bayesian probability</a> that updates over time to reflect whether anyone else has stepped up yet. The longer we wait with no-one stepping up, the closer our probability gets to one.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-Uo-HxZHrACk/V54Gq0uOxUI/AAAAAAAABtY/fNr4UNWCY2AhEJGdbLECGRf0Gz7aUYTCgCLcB/s1600/p-next.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://2.bp.blogspot.com/-Uo-HxZHrACk/V54Gq0uOxUI/AAAAAAAABtY/fNr4UNWCY2AhEJGdbLECGRf0Gz7aUYTCgCLcB/s1600/p-next.png" /></a></div>Of course, if you wait too long, someone behind you might assume you're not in the queue after all and try to go ahead of you. But that's a topic for another blog.<br /><br /><br /><br /><b>Nothing's so simple that it can't be made complex</b><br /><br />I've written about queuing before, in the context of a mathematical <a href="http://oatzy.blogspot.co.uk/2011/01/model-cafe.html">model of a cafe</a>.<br /><br />The long and short of it is this - people arrive at random (following a <a href="https://en.wikipedia.org/wiki/Poisson_distribution">Poisson distribution</a>) and join the queue with some probability (see above). Each iteration, some people are served, some join the queue, some get tired of waiting and leave, etc.<br /><br />I'm going to iterate in 5 min time-step and say that a haircut takes 10-25 minutes (i.e. 2-5 steps). The exact duration is randomly generated for each customer. <br /><br />I'm going to say that on average one person shows up every 10 minutes (0.5 per step). Here's an <a href="http://docs.scipy.org/doc/numpy/reference/generated/numpy.random.poisson.html">example</a> of how that might look over 12 steps (one hour), using the Poisson distribution: <i>[2, 0, 2, 0, 0, 0, 1, 0, 0, 1, 0, 0]</i><br /><br />I set up the simulation so that you specify some number of steps for the barbers to be considered 'open'. After that, no more people are added to the queue, but the simulation keeps running until the queue is empty.<br /><br />To begin with, I made it so that everyone who arrived stayed. <br /><br />With 2 workers, capacity 10, and an arrival rate of 0.5, the queue length typically stayed below 3. The highest I saw it go was 7, which is still comfortably within capacity.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-VjN2AK9oQ84/V50OD0tNBpI/AAAAAAAABsw/eepI7iZX9I4KWu1p6oD_zKuMlK2cVY7tACLcB/s1600/queue-size-no-leaving.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://2.bp.blogspot.com/-VjN2AK9oQ84/V50OD0tNBpI/AAAAAAAABsw/eepI7iZX9I4KWu1p6oD_zKuMlK2cVY7tACLcB/s400/queue-size-no-leaving.png" width="400" /></a></div>Above is an example of how the queue size varied over time in a particular simulation.<br /><br />Increasing the arrival rate to 0.7, the average maximum queue length goes up into the low teens. And when the rate goes up to 1, the maximum queue length goes all the way up into the 30s.<br /><br /><i>Homework question</i> - how does maximum queue length vary as a function of number of workers and arrival rate?<br /><br />As I mentioned, those simulations assumed that everyone stuck around. Once you turn on probabilistic leaving, things get a bit more interesting.<br /><br />I tried both versions of the leaving probability. The result was largely the same, except that sunk-cost people tend to leave sooner - average wait before leaving 2.6 for sunk vs 7.4 for non-sunk. This is what we'd expect - people adhering to sunk cost will tend to leave before they get too invested.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://2.bp.blogspot.com/-wxwpzkhwxt8/V50OWWjWymI/AAAAAAAABs4/FkLwwV17BVkn-qec3HlyZM1rkwsK0TRuwCLcB/s1600/queue-size-w-leaving.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="223" src="https://2.bp.blogspot.com/-wxwpzkhwxt8/V50OWWjWymI/AAAAAAAABs4/FkLwwV17BVkn-qec3HlyZM1rkwsK0TRuwCLcB/s400/queue-size-w-leaving.png" width="400" /></a></div><br />In the above example, orange is a time step when someone left the queue, and red is when a newcomer decided not to join the queue. I tuned the probability constants so that people don't start leaving until we're close to or at capacity, as we'd expect in real life.<br /><br /><br /><br /><b>You and I have different ideas of what constitutes 'interesting'</b><br /><br />Going back to the original description of the barber's queue, here's an example of a full queue (bracketed numbers are queue positions)<br /><br /><blockquote class="tr_bq"><i>542b5af6 (3), 33e323cf (5), 69e60241 (6), b3f12010 (0), fc89732e (7), 991f8709 (1), a0cb93cc (2), 17186c75 (4), 57269a3e (8), 8f1b61ca (9)</i></blockquote><br />Notice how, even with the basic seat picking strategy (take the first available), the members aren't in a predictable ordered.<br /><br /><br />When we look at the waiting times, we can see some interesting things. For example <br /><blockquote class="tr_bq"><i>...<br />1c7081d1 waited 7.0<br />34 1<br />35 3<br />50be68cf waited 9.0<br />36 3<br />37 4<br />26ca11b7 waited 3.0<br />38 3<br />39 3<br />a571a7da waited 5.0</i><br /><i>...</i></blockquote><br />Here we have a person who had to wait 9 steps (45mins) to be served, followed by someone who only had to wait 3 steps (15mins). Which just goes to show, how long you wait in a queue is very much a matter of timing and luck.<br /><br /><br />It's also interesting that you can run the simulation multiple times with the exact same settings, and one time the queue will never go higher than 4, while in the the next it'll go as high as 13. This is <a href="https://en.wikipedia.org/wiki/Complexity">complexity</a> at work - various small random factors in the model interacting to produce wildly different outcomes.<br /><br /><br />So yeah. If you're interested, you can see the full <a href="https://dl.dropboxusercontent.com/u/4635169/barber.py">code here</a>. I may have gotten carried away with the object-orienting.<br /><br /><br />Oatzy.<br /><br /><br /><br /><b>[Post-Script</b><br /><br />My boss recently pointed out that it'd been over a year since my last blog post. That was another perk of being unemployed - more time to come up with dumb blog posts. Anyway, here's a quick update on some stuff.<br /><br /><br /><b>Pirate Game</b><br /><br />The last blog post was about the making of an Android game - The Pirate Game.<br /><br />The game is now finished-ish and has been released in 'beta' on the <a href="https://play.google.com/store/apps/details?id=com.cavillum.pirategame.android">Play Store</a>.<br /><br />In the <a href="http://oatzy.blogspot.co.uk/2015/06/the-pirate-game.html">previous post</a>, I mentioned that the game would eventually get a less utilitarian design. I ended up making that design myself (because I'm a control freak). I'm pretty pleased with how it turned out.<br /><div class="separator" style="clear: both; text-align: center;"><a href="https://4.bp.blogspot.com/-WkgHFr8HB9k/V50Q6sAZ13I/AAAAAAAABtI/jV3A7fcmRjghLh2Ats4bs8Y14gL3_MI4ACLcB/s1600/pg-board-mock.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://4.bp.blogspot.com/-WkgHFr8HB9k/V50Q6sAZ13I/AAAAAAAABtI/jV3A7fcmRjghLh2Ats4bs8Y14gL3_MI4ACLcB/s320/pg-board-mock.png" width="180" /></a></div>Also, following a... less than positive review, I added some new game play modes.<br /><br />I never did figure out multiplayer, though. If I ever get the time or inclination to go back to the game, that'll be on the todo list. But for the time being, I don't anticipate any updates to the game. Certainly not any time soon.<br /><br /><br /><b>New Job</b><br /><br />So yeah. I finally got a job. I'm now a Software Developer at a company called <a href="http://pixitmedia.com/">Pixit Media</a>.<br /><br />The company sells large scale 'storage solutions' to companies primarily in the VFX industry, as well as universities and other such people that do high performance computing.<br /><br />What I personally work on is primarily a Python API for the <a href="http://www-03.ibm.com/systems/uk/storage/spectrum/scale/">IBM Spectrum Scale</a> (GPFS) filesystem. You can see the <a href="http://arcapix.com/gpfsapi/">API docs online</a>. I wrote a decent amount of the documentation (and the code that's being documented).<br /><br />In particular, the '<a href="http://arcapix.com/gpfsapi/list_processing_guide.html">Getting Started With List Processing</a>' guide. Admittedly the topic is a bit niche - I doubt many readers of this blog even know of GPFS, let along have a cluster with it installed. But you might still find it interesting; you can learn some stuff about <a href="https://en.wikipedia.org/wiki/MapReduce">MapReduce</a> - a technique for taking advantage of parallelism when processing large data-sets. <br /><br />There's also the '<a href="https://github.com/arcapix/gpfsapi-examples">example scripts</a>' repository - scripts written to use the API, some of while I wrote. But, again, they're a bit niche.<br /><br />] Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-89232834081043337022015-06-28T19:39:00.002-07:002015-07-07T05:11:38.153-07:00The Pirate GameA few months ago, my friend was telling me about this mobile game he wanted to make. He, and some of our other friends, are teachers, and this is a game they play with their students. The students apparently love it. And if there were an app version, they'd be willing to pay 59p for it. Or so they say.<br /><br />Now, as he was explaining the game to me, I thought maybe he was building towards asking for my help. I felt almost betrayed that he didn't. Several years ago, <a href="http://oatzy.blogspot.co.uk/2011/02/designing-for-free.html">I'd built a website</a> for this friend that he'd had nothing but praise and gratitude for.<br /><br />A month or so later, I got a text message - "How are you at programming?" For whatever reason, the guy they had originally 'hired' wasn't doing it anymore.<br /><br />"I'm capable", I replied. I'd done a course in Java at university. Admittedly, that was 'Java for Mathematicians'. And it was also almost 8 years ago. But how hard could it be to pick up again? Just like coding a bicycle. Or something like that...<br /><br /><br /><br /><b>The Game</b><br /><br />As the title of the blog suggests, its called 'The Pirate Game'. In the classroom it's <a href="https://www.tes.co.uk/teaching-resource/the-pirate-game-end-of-term-activity-6258063">played on paper</a>.<br /><br />Basically, you have a 7x7 grid filled with items - mostly coins, but also some items that let you do other things. There are attacks that let you, for example, rob or kill other players. There's a shield and a mirror that let you defend against attacks. There's a bank item that let's you save whatever points you have from being stolen, etc. There's a bomb that blows you up (sets your points to zero). And so on.<br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-JnzEKgNrvmE/VY2PUjgsYrI/AAAAAAAABfU/wAreqS0rSqE/s1600/grid.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="http://1.bp.blogspot.com/-JnzEKgNrvmE/VY2PUjgsYrI/AAAAAAAABfU/wAreqS0rSqE/s320/grid.png" width="315" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">This is the prototyping design. The final product will look less utilitarian.</td></tr></tbody></table><br />At the start of the game, the players arrange the items in their grids to their liking. The teacher (game) then calls out random squares, and the players get whatever is in that square on their own grid. In the classroom, if a player gets an attack item, they have to raise their hand and tell the teacher who they want to use it on. The winner is whoever has the most points when all the squares have been called. <br /><br /><br /><br /><b>Making Games 1: The Right Tools</b><br /><br />In a <a href="http://oatzy.blogspot.co.uk/2015/02/designing-general-relativity-based.html">previous blog</a>, I asserted that making mobile games was difficult. In fact, it turns out to not be so bad with the right framework. In this case I used the popular <a href="http://libgdx.badlogicgames.com/">LibGdx</a>. What's particularly nice about it is that there are a lot of how-to guides, and there's plenty of help for when things go wrong.<br /><br />As they say, programming is 1% inspiration, 99% Googling.<br /><br />For anyone interested in making their own Android game, I found these guides particularly helpful,<br /><br /><a href="http://www.kilobolt.com/zombie-bird-tutorial-flappy-bird-remake.html">LibGdx Zombie Bird Tutorial</a><br /><a href="https://www.geekbooks.me/book/view/libgdx-game-development-essentials">LibGdx Game Development Essentials</a><br /><a href="http://fortheloss.org/tutorial-set-up-google-services-with-libgdx/">Set up Google Services with LibGdx</a><br /><br /><br /><br /><b>HAL9000</b><br /><br />I got a first working version of the game done in the space of about a month - this was just the basic game mechanics, a bare-bones interface, and a single computer opponent that I could play against to check that the mechanics were doing what they were supposed to.<br /><br />In those first few tests of the game, I found that the computer player kept beating me - at one point, 7 to 1. This seemed strange - neither I nor the computer could choose who we attacked, or which defences we used. So really, there wasn't anything either of us could do to influence the outcome of the game. The winner should have been totally random.<br /><br />So loosing 7 times out of 8 seemed significant to me. As far as I could tell, the mechanics were working correctly. The only theory I could come up with was that maybe there was some advantage in the order in which we took our turns. <br /><br />To try and figure out what was going on, I created a simulation. Basically, I re-wrote a very stripped down version of the player and game mechanics in <a href="https://www.dropbox.com/s/dpcenzal4tn7i13/pirate-sim.py?dl=0">Python</a>. I then had two computer players play against each other in 10,000 matches. The results from this were - Player 1: 5005, Player 2: 4995.<br />In other words, the winner <i>was</i> just random chance. And turn order didn't matter.<br /><br />If nothing else, this was a lesson in not drawing conclusions from such a small data set - it's not really statistically significant if there are only 8 data points (that's a standard error of ~3). After playing more games, the wins did end up averaging out.<br /><br /><br /><br /><b>Making Games 2: Coordinating Players</b><br /><br />Development progressed. I got the full game mechanics working, and added more computer players (Clu and Ultron). I was now able to choose who I wanted to attack and what defences I wanted to use. <br /><br />But the ultimate goal for the game is to let users play against their (human) friends. This meant I had to do a massive re-write to generalise the interaction mechanics.<br /><br />Okay, lets look at an example of an interaction. Say I want to swap points with another player. First, my device needs to pop-up a player select dialog. Once I pick a target, the game needs to inform that player that I'm trying to swap points with them. If that player has a shield, their device needs to pop-up a dialog asking if they want to use it. The game then needs to inform me of my target's response - and if the target doesn't defend them self, we need to tell each other what our respective points are so that we can complete the swap.<br /><br />That's a lot of back and forth to handle. Here are the rough diagrams I drew when I was trying to get the mechanics straight in my head.<br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-eE3MMte_JlY/VY3ej4R7FCI/AAAAAAAABf0/M2gT7ik2zWM/s1600/diagrams.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="http://2.bp.blogspot.com/-eE3MMte_JlY/VY3ej4R7FCI/AAAAAAAABf0/M2gT7ik2zWM/s400/diagrams.jpg" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">I doubt this helps clarifies things for anyone else.</td></tr></tbody></table><br />So the way the interaction works in the code is, the attacker sends their target a data object telling them who the attacker is, what attack they're trying to use, and what points they have (though the points aren't visible to the target). Once the target has chosen their defence, they complete their side of the attack processing - so in the swap example, if the target doesn't defend, they set their points to those of the attacker.<br /><br />The target then sends the attacker's original data, along with the defence they chose (if any) and their (pre-attack) points to all the other players. If the recipient is the original attacker, they complete their side of the attack. Then, all players are shown a notification telling them what happened.<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-9KyFfX3ba3E/VY3akG7fvpI/AAAAAAAABfo/2tR5fgCPUTg/s1600/dialog-ex.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="http://3.bp.blogspot.com/-9KyFfX3ba3E/VY3akG7fvpI/AAAAAAAABfo/2tR5fgCPUTg/s320/dialog-ex.png" width="220" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">I figured I should give the computer players more piratical names.</td></tr></tbody></table><br />All this coordination is done by a turn handler class. And what's nice is the computer players can also interact with each other, and with the local human player, via the turn handler.<br /><br />All I need to do now is set up the stuff that actually sends the data between network players.<br /><br /><br /><br /><b>Game Theory</b><br /><br />Through testing (looking for bugs and the like), I've played this game A LOT. And I've gotten a pretty good feel for how it works. It's actually quite fascinating when you really get into it. (Or maybe that's just Stockholm Syndrome talking).<br /><br />Information is important to the game. Without it, players would be forced to make their moves at random. And when that happens, winning becomes mostly random chance. This is why players are shown notifications when other players interact - it allows them to strategise.<br /><br />The most basic strategies are things like - rob people who have lots of points, don't try to rob people who have just been killed (they don't have any points to take), defend yourself when you have a lot of points (if you can). Really, this is just being sensible.<br /><br />Then there are more subtle strategies. For example, sometimes it's better to lose a lot of points to the bomb or to being killed (removing those points from the game) rather than letting another player take them. Because, you don't need a lot of points to win, you just need more than everyone else.<br /><br />Given the inherent randomness of the game, a lot of how you play will come down to how risk-averse you are. For example, a particularly risky strategy might be to let an opponent rob you (saving your defences), in the hopes that you can steal back your points, and more, later on. <br /><br />When you play against humans (rather than AI), a whole bunch of social factors can come into play too. From what I hear, in the classroom, students tend to disproportionately target any members of staff that are playing. Though I can't imagine why.<br /><br />One other interesting feature of the game is that it sometimes forces players to make disadvantageous moves. There's the bomb item that will take away your points when it inevitably comes up. And if you're in first place, the swap item forces you to give your points, and the lead, to another player. <br /><br />So yeah, the game's not as simple as it might seem on the face of it. <br /><br /><br /><br /><b>Artificial Intelligence</b><br /><br />In the first fully featured version of the game, the computer players made their decisions completely randomly. And that was fine - the game is perfectly playable with random opponents, it's not too easy, and the randoms can and will beat you on occasion. Sometimes by an embarrassing margin. <br /><br />But random players also do things that don't make sense. So I wanted to create some computer players that used basic strategies to play more intelligently.<br /><br />For interactions, these intelligent computer players (AI) use literal hit lists and avoid lists. As mentioned above, when two players interact, that information is sent to all other players. For humans, this information is displayed as a notification. For the AI, the information is processed to build/modify the hit and avoid lists.<br /><br />For example, if Player 1 steals a lot of points from Player 2, then Player 1 is added to the hit list and Player 2 is added to the avoid list. If another player then kills Player 1, Player 1 is removed from the hit list and added to the avoid list. And so on.<br /><br />If the AI then gets an attack square, they'll first check their hit list for a target. If the hit list is empty, they'll chose a random opponent who isn't on the avoid list. And so on.<br /><br />There are also various basic heuristics for choosing which defences to use (and when), and for choosing a square when they get 'Choose Next'.<br /><br />As they are, the AI are quite formidable, and at times frustratingly so. In general, I think it's better to have a mix of random and intelligent computer players. The AI offer a challenge, while the chaotic influence of the random players keeps things interesting.<br /><br /><br /><br /><b>Measuring Difficulty</b><br /><br />In the game's current form, you can play against 2-7 computer players, and these players can be either random or 'intelligent' (as above). That means 33 unique opponent set-ups. Which raises the question - how can we rate the difficulty of any given set-up. <br /><br />Obviously it's harder to win when there are more opponents. Specifically, the probability of a random player winning in a game of N random players is 1/N. Think of it like this - if all the players behave in the same way, they are indistinguishable. And if they're indistinguishable, they must all have the same probability of winning (turn order doesn't matter).<br /><br />The same logic applies to intelligent computer players (AI) - since they all follow the same set of rules, they must also be indistinguishable from each other. Therefore, the odds of an AI winning in a game of N AI players must also be 1/N .<br /><br />But what happens when there's a mix? How much harder are AI to beat than randoms?<br /><br />To answer that, we can run some more simulations. This time, instead of re-writing code in Python, I created a modified version of the turn handler (see above) within the game project itself. Essentially I just removed all code that related to human players, and added a few bits to track statistics.<br /><br />I set up the 33 different player configurations, ran 10,000 matches for each, and worked out the probability of winning for an AI player - for simplicity, we're assuming that a human player (playing strategically) is roughly equivalent to an AI. <br /><br />From those simulations I made this lovely surface plot (using <a href="http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#toolkit-mplot3d-tutorial">matplotlib</a>).<br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-UNO6yesvtp8/VY17dCqvhPI/AAAAAAAABfE/LpB2eX4d7Bc/s1600/sim-surface-edit.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="363" src="http://4.bp.blogspot.com/-UNO6yesvtp8/VY17dCqvhPI/AAAAAAAABfE/LpB2eX4d7Bc/s400/sim-surface-edit.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Configurations with more than 7 opponents have been set to zero.</td></tr></tbody></table><br />And what we find is AI are roughly twice as hard to beat as randoms - notice the surface is higher on the right hand side (when there are fewer AI).<br /><br />In other words, your odds of winning in a match against two AI is roughly equal to your odds of winning in a match against four randoms. For simplicity, we're going to assume that the relationship is exactly two to one.<br /><br />We can now calculate an approximation of the odds of winning as<br /><br />\[ p(R, I) = \frac{2}{R + 2(I+1)} \]<br /><br />And if we compare these probabilities to those from the simulations, we find that they are within standard error (order 0.01 for 10,000 trials).<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-bueIr2TefEQ/VZAWb3OzKlI/AAAAAAAABgM/J-2XZLbG2U0/s1600/prob-surface-edit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="363" src="http://4.bp.blogspot.com/-bueIr2TefEQ/VZAWb3OzKlI/AAAAAAAABgM/J-2XZLbG2U0/s400/prob-surface-edit.png" width="400" /></a></div><br />To get a difficulty rating, we can just turn the probability on it's head, and normalise by the probability for the easiest set-up (2 random opponents). That is,<br /><br />\[ D = \frac{p_0}{p} = \frac{R + 2(I+1)}{4} \]<br /><br />This gives us a difficulty rating between 1 and 4, which lets us easily divide the difficulty into three levels - easy, medium, and hard. Though, if your odds of winning in the easiest possible game is only 50:50, can it really be considered 'easy'?<br /><br />Another nice property of this difficulty rating system is that the difficulty ratios match the probability ratios - that is, your odds of winning a 2 are half your odds of winning a 1, and so on. Specifically, your odds of winning are roughly \( \frac{1}{2D} \)<br /><br />Incidentally, if we assume our human player instead makes their moves completely at random, then their odds of winning are lower overall - \( p = \frac{1}{R+1+2I} \) - but the difficulty stratification works out the same.<br /><br /><br /><br /><b>Conclusion</b><br /><br />Ordinarily, I'd include my code for this sort of thing. But in this case, doing so might undermine my (and my friends') income. And they very kindly told me that I'd get the majority of the profits. Whatever those may ultimately be.<br /><br />So far we've made a whopping 7p (!) just from having adMob set up in the test builds.<br /><br />But before we can make any (real) money, we have to actually finish the game. The single player version is mostly done, and should be released in the near future. The last big thing to do is the UI (which is someone else's <s>problem</s> job).<br /><br />Then the hard part is going to be setting up multi-player with Google Play Services. In particular, because there aren't any good guides (that I can find) for setting up Google multi-player with LibGdx.<br /><br />When it is done, I'll post links and such here for anyone that might be interested.<br /><br /><i><b>[edit]</b></i> - If you want to try the current beta, you can get it <a href="https://play.google.com/store/apps/details?id=com.cavillum.pirategame.android">here</a>. <br /><br />Also, given that making an Android game has turned out to be much easier than I expected, I may in fact make the previously discussed relativity game myself (once the Pirate Game is finished). In that case, I might also provide my code.<br /><br /><br /><br />Oatzy.<br /><br /><br />[I'd love to make an AI that uses machine learning to counter human players' personal strategies.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-45286520807380221392015-03-12T11:21:00.000-07:002015-03-14T16:29:00.214-07:00Watch Face Designs for Android Wear (and How to Make Them)In a previous <a href="http://oatzy.blogspot.co.uk/2015/01/metric-and-binary-clocks-for-android.html">pair</a> of <a href="http://oatzy.blogspot.co.uk/2015/01/follow-up-binary-proof-and-trinary-clock.html">blogs</a>, I designed some clock widgets for Android, using <a href="https://play.google.com/store/apps/details?id=org.zooper.zwfree&hl=en_GB">Zooper</a>. At the end, I said that if I ever got a smart watch I'd remake them for <a href="http://www.android.com/wear/">Android Wear</a>.<br /><br />Well, here I am following up on that promise. I've also thrown in a few new designs for good measure.<br /><br />Now, these are mostly concept watch faces - which is to say, they're more 'look at this cool thing you can do', than the sort of watch faces that you'd actually want to use.<br /><br />As with the previous blogs, I've provided download links for my designs (which you should be able to customise), along with general instructions for how they're constructed, as well as some code snippets.<br /><br />I know that a lot of the people who read this post will have come here from trying to google how to do something. As someone who does the same, I want this to be as helpful as possible.<br /><br />Note, these faces were designed in particular for my watch - <a href="http://www.motorola.co.uk/consumers/moto-360-header-gb/Moto-360/moto360-pdp-gb.html">Moto 360</a>. So if you have a different device (in particular, a square faced device) you might want/need to tweak some stuff.<br /><br /><br /><b>Watchmaking Apps</b><br /><br />There are two popular apps for making Android Wear watch faces - <a href="https://play.google.com/store/apps/details?id=com.jeremysteckling.facerrel">Facer</a> and <a href="https://play.google.com/store/apps/details?id=slide.watchFrenzy">WatchMaker</a>. I've tried (and paid for) both. <br /><br />Facer is very bare-bones - the only objects available are text, shapes and images, and the only customisation options are object size, position, rotation, colour, and opacity. I think it's aimed more at people who want to import images to construct their faces.<br /><br />WatchMaker has many more build-in objects, including dials, hands, weather, battery, countdowns, series, etc. There are also more customisation option to play with (and more still if you pay for Premium).<br /><br />Facer uses the same syntax and tags as Zooper, so if you're familiar with that you might prefer Facer. But that also means it has the same shortcoming, in particular with respect to if-statements.<br /><br />Watchmaker, on the other hand, uses the programming language '<a href="http://www.lua.org/">Lua</a>' for its coding. I'm not familiar with Lua as a whole, but I found it easy to pick up here, and easier to work with than the Zooper-style syntax.<br /><br />Of the two apps, I personally prefer WatchMaker - I like all the extra built-in objects and customisations, and I find the Lua syntax, and the way WM handles layout (see below) easier to work with. <br /><br />This means, then, that most (but not all) of the download links and code snippets in this blog are going to be for WatchMaker. You should be able to recreate a lot of the designs in Facer though.<br /><br /><br /><b>Design Layout</b><br /><br />Facer and WatchMaker use different coordinate origins - Facer positions objects relative to the top-left corner of the screen, where WatchMaker positions objects relative to the centre of the screen.<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-LKELAOD0H3s/VPuNksigaBI/AAAAAAAABWs/R6U54EHMDYc/s1600/layouts.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-LKELAOD0H3s/VPuNksigaBI/AAAAAAAABWs/R6U54EHMDYc/s1600/layouts.png" height="200" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Notice that the y-axis is upside down in both apps.</td></tr></tbody></table><br />Now, for watch design, we'll often need to convert the positions of objects from <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">polar coordinates</a> - distance from the centre of the screen (r), and rotation about the centre (\(\theta\)) - to <a href="https://en.wikipedia.org/wiki/Cartesian_coordinate_system">Cartesian coordinates</a> (x, y).<br /><br />Traditionally, the conversion is<br /><br />\[ x = r \cos(\theta) \\<br />y = r \sin(\theta) \]<br />But this positions objects relative to the positive x-axis, or 3 o'clock (blue). Really, we want to position objects relative to the negative y-axis, or 12 o'clock (yellow).<br /><br />To correct this, we have to subtract 90 degrees from the angle, \(\theta\). Or, alternatively, we can use some <a href="https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities">trigonometric identities</a> to rewrite the conversion as<br /><br />\[ x = r \cos(\theta-90) \equiv \ r \sin(\theta) \\<br />y = r \sin(\theta-90) \equiv \ -r\cos(\theta) \]<br />In Facer, we also have to add an offset of about \(x_0 ,\ y_0 = 160px\) to x and y, so that objects are positioned relative to the centre of the screen (rather than the top left corner).<br /><br />Note that in both apps, the functions 'sin' and 'cos' take variables in <a href="https://en.wikipedia.org/wiki/Radian">radians</a>, whereas the angles given by tags are in degrees. This means we have to include 'rad()' (Facer) or 'math.rad()' (WatchMaker) whenever we use trig functions.<br /><br />Finally, it's worth noting that (for the Moto 360, at least) Facer gives us a design area of about 320px, matching the watch's screen dimensions. WatchMaker, on the other hand, gives us around 520px. This means that faces designed in WatchMaker will be scaled down on the watch itself, and in some cases, this leads to pixelation.<br /><br />Anyway, with all that in mind, lets look at some designs.<br /><br /><br /><br /><b>> A Bunch of Binary</b><br /><br /><br /><b><i>Proportional Binary</i> </b><a href="https://www.dropbox.com/s/0fwh94bkgxybsmt/binary-watch.watch?dl=0">[WM]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-sAhnYN_zmkw/VPzZJZoRUeI/AAAAAAAABXk/tBUQEq-MTk4/s1600/Screenshot_2015-03-08-22-22-08.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-sAhnYN_zmkw/VPzZJZoRUeI/AAAAAAAABXk/tBUQEq-MTk4/s1600/Screenshot_2015-03-08-22-22-08.png" height="178" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">22:22</td></tr></tbody></table><br />I have to start, of course, with my pet design - already available in <a href="http://oatzy.blogspot.co.uk/2011/01/needlessly-incomprehensible-clock.html">javascript</a>, and for <a href="http://oatzy.blogspot.co.uk/2015/01/metric-and-binary-clocks-for-android.html">Zooper</a>.<br /><br />Each ring segment is a binary bit, sized in proportion to the value it represents - the 4 minutes bit is twice as big as the two minutes bit, etc. The outer ring is hours (24h), the inner is minutes.<br /><br />In Zooper, this design was constructed using curved rectangles, which neither of the face making apps support. However, in <a href="https://play.google.com/store/apps/details?id=slide.watchFrenzy.premium&hl=en_GB">WatchMaker Premium</a>, we can get more or less the same effect by creating a circle with a 'Segment Between' shader, and hiding the centre of the segment with a circle that's the same colour as the face background. <br /><br />Once we have that, the rest of the face construction is essentially the same as the one described in the previous blog. It's set up so that the segment opacity is 100% if the bit (segment) is 'on', and 15% if 'off'. <br /><br />A quick reminder, the n-th bit is off if \( x \bmod 2^{n+1} \lt 2^n \), and on otherwise. So for the twos bit in the minutes ring we have<br /><br /><pre class="brush:plain">% opacity in range: {dm}%4 < 2 and 15 or 100</pre><br />And so on for all the other segments.<br /><br />Unfortunately, this is one of the designs that suffers significant pixelation on the actual watch screen. A slight improvement was to make the rings narrower such as below. Here I've also added a seconds ring.<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-3IzTBu2cjQM/VPzZhh6L7tI/AAAAAAAABXs/gQ9lhGRJfpU/s1600/Screenshot_2015-03-08-22-22-44.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://1.bp.blogspot.com/-3IzTBu2cjQM/VPzZhh6L7tI/AAAAAAAABXs/gQ9lhGRJfpU/s1600/Screenshot_2015-03-08-22-22-44.png" height="180" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><a href="https://www.dropbox.com/s/9wrc3ix6wr2xnx3/binary-watch-thin.watch?dl=0">[download]</a></td></tr></tbody></table><br />In this case we could put some other information in the middle of the screen and use the binary as decoration.<br /><br /><br /><b><i>Binary Rings</i> </b><a href="https://www.dropbox.com/s/k7jku8myy3wdclr/Binary%20Rings.face?dl=0">[Facer]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-009sZ4vow34/VPzZ-W2iyLI/AAAAAAAABX0/kPULMiwfvW8/s1600/Screenshot_2015-03-08-22-17-52.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-009sZ4vow34/VPzZ-W2iyLI/AAAAAAAABX0/kPULMiwfvW8/s1600/Screenshot_2015-03-08-22-17-52.png" height="180" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">22:17</td></tr></tbody></table><br />We can actually do an approximation of the previous design in Facer. This is done with triangles, that are strategically covered with circles/rings. However, because of how this construction works, the layout is a little restricted in how broad and close together the rings can be.<br /><br />In this case, it's easier to have all the bits/segments be the same size. We could possibly have proportionally sized bits like above, but it'd be a lot of fiddling. On the other hand, Facer doesn't seem to have the pixelation issue, so it could be worth the effort.<br /><br />Again, the opacity of the bits is changed as described above and in the previous blog. For example<br /><br /><pre class="brush:plain">Opacity: $(#Dm#%4)<2?15:100$</pre><br />Aside - It's not obvious, but the brackets in the above are important. In general, if you find your code isn't behaving in Facer, try copy-pasting it into a text object to see what it's being interpreted as. Usually you can solve problems by adding/removing brackets, or by looking for any spaces that might have been auto-inserted after punctuation marks.<br /><br /><br /><b><i>Binary Numbers</i> </b><a href="https://www.dropbox.com/s/yfoxak1ft5zvh3a/Binary%20Numbers.face?dl=0">[Facer]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-XBTZ2MyoxzY/VPzaHx36acI/AAAAAAAABX8/gx9uriAbL40/s1600/Screenshot_2015-03-03-21-30-07.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-XBTZ2MyoxzY/VPzaHx36acI/AAAAAAAABX8/gx9uriAbL40/s1600/Screenshot_2015-03-03-21-30-07.png" height="180" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">21:30</td></tr></tbody></table><br />This is the first face I made for Android Wear. The bits are all individual text objects, with their values set according to that method I keep banging on about. For example, the twos minute bit is a text object with value<br /><br /><pre class="brush:plain">Text: $(#Dm#%4)<2?0:1$</pre><br />The face shown above is 24hour format, where the leftmost hours digit is a dummy bit. An alternative might be to do 12hour format, with an am/pm bit.<br /><br />Aside - I had this idea for a face where the background is an image of a circuit board with two rows of those little square LEDs, which could be 'illuminated' to represent the time in binary. But I'm not much of an artist, so I doubt I could create a decent background image.<br /><br /><br /><b><i>Trinary</i> </b><a href="https://www.dropbox.com/s/xhpp14emhzupoyp/trinary-watch-2.watch?dl=0">[WM]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-acXMUKpuUqQ/VPzab_Zd1bI/AAAAAAAABYE/pY8i_dI4wis/s1600/Screenshot_2015-03-08-21-35-46.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://1.bp.blogspot.com/-acXMUKpuUqQ/VPzab_Zd1bI/AAAAAAAABYE/pY8i_dI4wis/s1600/Screenshot_2015-03-08-21-35-46.png" height="180" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">21:35</td></tr></tbody></table><br />In trinary, or base 3, each bit (trit?) can have one of three values - 0, 1, 2. In Zooper, I handled this by using curved progress bars, but that doesn't really work in WatchMaker. In this case, I had to use pairs of segments for each bit.<br /><br />As with Zooper, I had the first of each bit pair change colour when the second was 'on', to make reading easier. Sometimes, though, WatchMaker doesn't change the segment colours like it should (possible bug?). If you prefer, you can download a version without colour changing <a href="https://www.dropbox.com/s/i038i9tjkx9uyt7/trinary-watch.watch?dl=0">here</a>. <br /><br />I explained how to do trinary in the <a href="http://oatzy.blogspot.co.uk/2015/01/follow-up-binary-proof-and-trinary-clock.html">previous blog</a>. A quick reminder - the n-th digit of a number in base b is given by \(\lfloor \frac{x}{b^n} \bmod b\rfloor\). <br /><br />As an example, for the pair of segments in the 3s minute bit we have<br /><br /><pre class="brush:plain">Segment 1: <br />% opacity in range: math.floor({dm}/3)%3 >= 1 and 100 or 15<br />Color: math.floor({dm}/3)%3 == 2 and '2cb7ff' or '1b74c0'<br /><br />Segment 2: <br />% opacity in range: math.floor({dm}/3)%3 == 2 and 100 or 15<br />Color: 1b74c0 </pre><br />Notice that when we use an if-statement with the colour, we have to wrap the colour value in apostrophes (inverted commas). We don't have to do this for a lone colour value.<br /><br />Similar to the binary design at the top, we have the pixelation problem on the actual watch, and again we can sort of help this by making the rings narrower.<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-NRlripLSnW8/VPzavmK4lDI/AAAAAAAABYM/kvVGOELOTec/s1600/Screenshot_2015-03-08-21-37-51.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://1.bp.blogspot.com/-NRlripLSnW8/VPzavmK4lDI/AAAAAAAABYM/kvVGOELOTec/s1600/Screenshot_2015-03-08-21-37-51.png" height="179" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><a href="https://www.dropbox.com/s/wr1nejd9479n8we/trinary-watch-thin.watch?dl=0">[download]</a></td></tr></tbody></table><br />Clocks in other bases (as seen in the previous blog) can be constructed in a similar way.<br /><br /><br /><br /><b>> Planets and Parametrisations </b><br /><br /><br /><b><i>Orbital</i> </b><a href="https://www.dropbox.com/s/7qr1ruf6q0yuyvd/orbital.watch?dl=0">[WM]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-Vhzm6oajVa0/VPzbbIfymxI/AAAAAAAABYk/y-cRz_L0T9U/s1600/Screenshot_2015-03-08-22-05-38.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-Vhzm6oajVa0/VPzbbIfymxI/AAAAAAAABYk/y-cRz_L0T9U/s1600/Screenshot_2015-03-08-22-05-38.png" height="181" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">10:05</td></tr></tbody></table><br />I'm a fan of 'cryptic' clock designs - clocks that let you tell the time, but don't make it obvious. In this design, the position on the planet relative to the sun is the hours hand, and the position of the moon relative to the planet is the minutes hand.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-jobZv6pVXl0/VP89Q9YXTmI/AAAAAAAABb4/3ABVtqqroHU/s1600/orbit-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-jobZv6pVXl0/VP89Q9YXTmI/AAAAAAAABb4/3ABVtqqroHU/s1600/orbit-alt.png" /></a></div>We set the planet's position as described in the <i>Layout</i> section<br /><br />\[ x = r_h \sin(\theta_h) \\<br />y = -r_h\cos(\theta_h) \]<br />For the moon, we work out it's position relative to the planet, then add that to the planet's position relative to the sun<br /><br />\[ x = r_h \sin(\theta_h) + r_m \sin(\theta_m) \\<br />y = -r_h \cos(\theta_h) - r_m \cos(\theta_m) \]<br />The code for this is<br /><br /><pre class="brush:plain">Planet:<br />x: 150*math.sin(math.rad({drh}))<br />y: -150*math.cos(math.rad({drh}))<br /><br />Moon:<br />x: 150*math.sin(math.rad({drh})) + 50*math.sin(math.rad({drm}))<br />y: -150*math.cos(math.rad({drh})) - 50*math.cos(math.rad({drm}))</pre><br /><br /><b><i>Orbital Numbers</i> </b><a href="https://www.dropbox.com/s/m8ep50de6qsxjla/orbital-numbers.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-CooP_apUVCE/VPzb6vNUUWI/AAAAAAAABYs/gc3KfpbxHqQ/s1600/Screenshot_2015-03-08-21-48-09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-CooP_apUVCE/VPzb6vNUUWI/AAAAAAAABYs/gc3KfpbxHqQ/s1600/Screenshot_2015-03-08-21-48-09.png" height="180" width="200" /></a></div><br />This is basically the same principle as the above, except we've replaced the sun with the hours, the planet with the minutes, and the moon with the seconds. The code is similar to above, except we replace {drh} with {drm} and replace {drm} with {drs}. We also need to make the orbital radii slightly bigger to avoid overlapping - 165 for the planet/minutes, and 55 for the moon/seconds.<br /><br /><br /><b><i>Elliptical Orbit</i> </b><a href="https://www.dropbox.com/s/tvo6uvbl2x0ppw8/elliptical-orbit.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/---khYFTUnZ4/VPzgvCMslYI/AAAAAAAABaA/OoSnzlglnKM/s1600/gif_u15.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/---khYFTUnZ4/VPzgvCMslYI/AAAAAAAABaA/OoSnzlglnKM/s1600/gif_u15.gif" height="200" width="200" /></a></div><br />Here the planet does an elliptical orbit once per minute. The orbit also <a href="https://en.wikipedia.org/wiki/Apsidal_precession">precesses</a> (rotates) over the course of an hour, so that the planet's <a href="https://en.wikipedia.org/wiki/Perihelion_and_aphelion">aphelion</a> (furthest point from the sun) points to the current minutes past the hour.<br /><br />An elliptical orbit, with aphelion pointing to 12 o'clock is given by<br /><br />\[ x = -r_x\sin(\theta) \\<br />y = y_0 + r_y\cos(\theta) \]<br />To make the orbit precess, we can use the 2D <a href="https://en.wikipedia.org/wiki/Rotation_matrix">rotation matrix</a> <br /><br />\[\begin{pmatrix}<br />\cos\phi & -\sin\phi \\<br />\sin\phi & \cos\phi<br />\end{pmatrix}\]<br />which gives use the rotated coordinates<br /><br />\[ x' = x\cos\phi - y\sin\phi\\<br />y' = x\sin\phi + y\cos\phi \]<br />where \(\phi\) is the angle of rotation - in this case the minutes past the hour. So for the code, the precessing orbit is given by<br /><br /><pre class="brush:plain">x: -75*math.sin(math.rad({drs}))*math.cos(math.rad({drm})) - (150*math.cos(math.rad({drs}))-80)*math.sin(math.rad({drm}))<br />y: -75*math.sin(math.rad({drs}))*math.sin(math.rad({drm})) + (150*math.cos(math.rad({drs}))-80)*math.cos(math.rad({drm}))</pre><br />The downside to this design is that it's pretty hard to tell where the aphelion is (i.e. the minutes past the hour) without watching the orbit for several seconds. Also, in it's current state, there's no way of telling the hour. Admittedly, this isn't great, design-wise. Really I just wanted to show off the rotation matrix.<br /><br />As a variation, we could have, for example, three elliptical orbits (one for each hand), to make a face modeled after the <a href="https://commons.wikimedia.org/wiki/File:Stylised_Lithium_Atom.svg#mediaviewer/File:Stylised_Lithium_Atom.svg">classic portrayal</a> of an atom (nucleus in the centre, with electrons whizzing around).<br /><br /><br /><b><i>Binary Orbit</i> </b><a href="https://www.dropbox.com/s/9kuag1hlk00yyj9/Binary%20Orbit%20WatchFace.face?dl=0">[Facer]</a> <a href="https://www.dropbox.com/s/zf5lfqra6sjzew0/binary-orbit.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-O5Uj8RZzi3g/VPzgR0IyZKI/AAAAAAAABZw/bDC7c-6nlOM/s1600/gif_u7.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-O5Uj8RZzi3g/VPzgR0IyZKI/AAAAAAAABZw/bDC7c-6nlOM/s1600/gif_u7.gif" height="200" width="200" /></a></div><br />This design was inspired by another <a href="http://oatzy.blogspot.co.uk/2015/02/designing-general-relativity-based.html">previous blog post</a>. Here we have a planet doing a figure-8 orbit around a pair of stars.<br /><br />To set the planet's position, the sideways figure-8 path can be parametrised as<br /><br />\[ x = r_x\sin\left(t\right) \\<br />y = r_y\sin\left(2t\right)\]<br />or in code<br /><br /><pre class="brush:plain">WM:<br />x: 225*math.sin(math.rad(6*{ds}))<br />y: 100*math.sin(math.rad(12*{ds}))<br /><br />Facer:<br />x: (155+140*sin(rad(6*#Ds#)))<br />y: (150-70*sin(rad(12*#Ds#))) </pre><br />Changing the signs in front of either or both of the sin-functions will change the direction of orbit.<br /><br />I was tempted to also have the stars (co)rotate - using the rotation matrix above to rotate the planet's orbit - so that they could act like an hours/minutes hand. But that seemed too much like hard work.<br /><br /><br /><b><i>Spiral</i> </b><a href="https://www.dropbox.com/s/vew07552jy7t1sk/spiral.watch?dl=0">[WM]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-36jYcIwECB0/VPzcFAep4zI/AAAAAAAABY0/_7LShWUInRw/s1600/Screenshot_2015-03-08-21-35-30.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://2.bp.blogspot.com/-36jYcIwECB0/VPzcFAep4zI/AAAAAAAABY0/_7LShWUInRw/s1600/Screenshot_2015-03-08-21-35-30.png" height="180" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">9:35pm</td></tr></tbody></table><br />In this design, the ball starts at the centre, and spirals outwards towards midday, then spirals back to the centre towards midnight. The ball's angular position gives the minutes past the hour.<br /><br />The spiral is parameterised as <br /><br />\[ x = r(t) \sin(\theta) \\<br />y = - r(t) \cos(\theta) \]<br />where the radius r is now a function of time. In code, this is<br /><br /><pre class="brush:plain">x: 17 + 420*({dtp}<0.5 and {dtp} or 1-{dtp}))*math.sin(math.rad({drm}))<br />y: -17 - 420*({dtp}<0.5 and {dtp} or 1-{dtp}))*math.cos(math.rad({drm}))</pre><br />where {dtp} is the fraction of the day that has passed. Notice that we can use a conditional as a variable. That's pretty handy.<br /><br />Originally, I didn't bother with the numbers (another cryptic design), but figured I should make it a little more readable. The numbers change at midday/midnight. Coding this is just a matter of creating text objects with values like<br /><br /><pre class="brush:plain">Text: {dh23}<12 and 11 or 1</pre><br />And so on.<br /><br /><br /><b><i>Heart</i> </b><a href="https://www.dropbox.com/s/dc64psaeigjgyi7/heart-clock.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-QWVCQvpqtjw/VPzgbaUx6lI/AAAAAAAABZ4/nlwZpOs753Y/s1600/gif_u6.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-QWVCQvpqtjw/VPzgbaUx6lI/AAAAAAAABZ4/nlwZpOs753Y/s1600/gif_u6.gif" height="200" width="200" /></a></div><br />Yes, I know, it looks ridiculous. When I realised I could have objects trace out any shape that can be <a href="https://en.wikipedia.org/wiki/Parametric_equation">parameterised</a>, my first thought was the <a href="http://mathworld.wolfram.com/HeartCurve.html">heart curve</a>.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-Ut9VcyPQRQM/VPuODZ2Tn-I/AAAAAAAABW0/GrYZYM09el8/s1600/heart-curve.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-Ut9VcyPQRQM/VPuODZ2Tn-I/AAAAAAAABW0/GrYZYM09el8/s1600/heart-curve.png" /></a></div>This has the equations<br /><br />\[ \begin{align*}x &= 16\sin(t)^3 \\<br />y &= 13\cos(t) - 5\cos(2t) - 2\cos(3t) - \cos(4t)\end{align*} \]<br />and can be coded as<br /><br /><pre class="brush:plain">x: 192*(math.sin(math.rad(6*{ds})))^3<br />y: -156*math.cos(math.rad(6*{ds}) + 60*math.cos(math.rad(12*{ds}) + 24*math.cos(math.rad(18*{ds}) + math.cos(math.rad(36*{ds})</pre><br />In the screenshot/gif, the heart graphic is just decoration. The ball doesn't trace out that exact shape, but it's close.<br /><br />I added a (poorly drawn) arrow as an hour hand. The shaft is a rectangle rotated using {drh24}. The triangles that make up the arrow head and <a href="https://en.wikipedia.org/wiki/Fletching">fletching</a> are moved around using the usual sin and -cos, and have to be rotated with {drh24} to make sure they point in the right direction.<br /><br /><br /><br /><b>> Analogue-esque</b><br /><br /><br /><b><i>Metric</i> </b><a href="https://www.dropbox.com/s/ijy209fhgtovya6/metric-clock.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-hL2WR2Ei7fE/VPza8AYJofI/AAAAAAAABYU/OeEhb-c0F1c/s1600/Screenshot_2015-03-08-21-29-11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-hL2WR2Ei7fE/VPza8AYJofI/AAAAAAAABYU/OeEhb-c0F1c/s1600/Screenshot_2015-03-08-21-29-11.png" height="180" width="200" /></a></div><br />Quick reminder - Metric, or 'decimalised' time redefines the day as being 10 metric hours long, with 100 metric minutes to the metric hour, and 100 metric seconds to the metric minute.<br /><br />Aside - this means a metric minute is equal to 1 milliday, and a metric second is 10 microdays. <br /><br />For the hours hand, we just set its rotation to '#DWFH#' (Facer) or '{drh24}' (WatchMaker) - even though the day is being defined as 10 hours long, the hours hand still only needs to do one revolution per day.<br /><br />For the minutes hand, we have to do the full conversion, as <a href="http://oatzy.blogspot.co.uk/2015/01/metric-and-binary-clocks-for-android.html">described</a> <a href="http://oatzy.blogspot.co.uk/2010/08/decimalising-time.html">previously</a>, then convert that to a rotation (in degrees). The code looks like this<br /><br /><pre class="brush:plain">WM: 360*((0.00011574*(3600*{dh23}+60*{dm}+{ds}))%1)<br /><br />Facer: (360*((0.00011574*(3600*#DH#+60*#Dm#+#Ds#))%1))</pre><br />Note that in Facer the whole expression has to be wrapped in brackets to be interpreted and evaluated as maths.<br /><br />The seconds hand is a little trickier. If we do the conversion as before, <br /><br /><pre class="brush:plain">WM: 360*((0.011574*(3600*{dh23}+60*{dm}+{ds}))%1)<br /><br />Facer: (360*((0.011574*(3600*#DH#+60*#Dm#+#Ds#))%1))</pre><br />the hand will point to the right time, but because it will only update every standard (non-metric) second, it will only 'tick' about 87 times per revolution (instead on 100 times).<br /><br />There is a crafty work around, though. First, we have to include milliseconds (WatchMaker) or 'smooth rotation for seconds' (Facer) in the conversion, so that the hand will update more frequently. We then use the 'floor' function to round down to the nearest whole number - this will make the hand only 'tick' once per metric second. The code looks like this<br /><br /><pre class="brush:plain">WM: 3.6*math.floor((1.1574*(3600*{dh23}+60*{dm}+{ds}+{dss}/1000))%100)<br /><br />Facer: (3.6*floor((1.1574*(3600*#DH#+60*#Dm#+#DWFSS#/6))%100))</pre><br />Alternatively, we could omit the 'floor' function, and have a smooth seconds hand.<br /><br /><br /><b><i>Backwards</i> </b><a href="https://www.dropbox.com/s/z7fclwkuzojoa30/backwards-clock.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-iyP6CXBPpBI/VPzbFd0dePI/AAAAAAAABYc/M_pCa5LKQHA/s1600/Screenshot_2015-03-08-21-22-53.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-iyP6CXBPpBI/VPzbFd0dePI/AAAAAAAABYc/M_pCa5LKQHA/s1600/Screenshot_2015-03-08-21-22-53.png" height="181" width="200" /></a></div><br />Here, I've tried to make the face design as close to the one on my bedroom wall as possible. Code-wise, this is just a matter of setting the hands' rotations to <br /><br /><pre class="brush:plain">WM: 360-{drh}, 360-{drm}, 360-{drs}<br /><br />Facer: (360-#DWFKS#), (360-#DWFMS#), (360-#DWFS#)</pre><br /><br /><b><i>Squares</i> </b><a href="https://www.dropbox.com/s/gqih1rkflylh1j2/squares.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-x4BqcBWzh8s/VPzcf_0YtKI/AAAAAAAABY8/E_QMLTq38mY/s1600/Screenshot_2015-03-08-21-29-55.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-x4BqcBWzh8s/VPzcf_0YtKI/AAAAAAAABY8/E_QMLTq38mY/s1600/Screenshot_2015-03-08-21-29-55.png" height="181" width="200" /></a></div><br />This was just a random idea I had, and is another good example of a cryptic clock. Each square represents a hand, and they're scaled by \(\sqrt{2}\) so that they always stay within each other. The code is basically the same as for any other analogue design - set the square rotations to '{drh}-45' / '(#DWFKS#-45)', etc.<br /><br />Probably I should have added indicators to show which corner of each square is the 'hand'. But I reckon you could figure out the time (or at least guess) without. I mean, the above is obviously showing 9:29:55.<br /><br /><br /><b><i>Spotlight</i> </b><a href="https://www.dropbox.com/s/d3dd76cdqaj76xl/spotlight.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-UgySc1m52Zc/VP3bpLZH0pI/AAAAAAAABa8/GnEItC_CwYM/s1600/Screenshot_2015-03-09-17-08-07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-UgySc1m52Zc/VP3bpLZH0pI/AAAAAAAABa8/GnEItC_CwYM/s1600/Screenshot_2015-03-09-17-08-07.png" height="178" width="200" /></a></div><br /><i>This is not my design</i> - I couldn't find the original source. You can actually download a version of this face from the <a href="https://play.google.com/store/apps/details?id=com.maize.spotlight&hl=en">PlayStore</a>. This is more for anyone who's curious about how to (re)create it. Plus, this way you can customise it to your heart's content, as you'll see further down.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-Y9EHznjQVL8/VPuM9vvAU5I/AAAAAAAABWc/UiE1OEnUCDQ/s1600/Spotlight-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-Y9EHznjQVL8/VPuM9vvAU5I/AAAAAAAABWc/UiE1OEnUCDQ/s1600/Spotlight-alt.png" height="297" width="320" /></a></div>The basic idea is to create a dial that is bigger than the watch face itself, and place the centre of the dial somewhere off-screen. We then move the dial in a clockwise circle around the outside of the face, as<br /><br /><pre class="brush:plain">x: -350*math.sin(math.rad({drh}))<br />y: 350*math.cos(math.rad({drh}))</pre><br />The hour line is just a rectangle with rotation {drh}<br /><br /><br /><b><i>Spotlight with Minutes</i> </b><a href="https://www.dropbox.com/s/bd703v7r0ae3lg8/spotlight-minutes.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-N2uJP54IPFU/VP3cEJv8RLI/AAAAAAAABbE/TwAAf_KP29Y/s1600/Screenshot_2015-03-09-17-08-12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-N2uJP54IPFU/VP3cEJv8RLI/AAAAAAAABbE/TwAAf_KP29Y/s1600/Screenshot_2015-03-09-17-08-12.png" height="180" width="200" /></a></div><br />One of the problems with the previous design is that there are only five minor markers between each hour marker - so in this context they're equivalent to 12 minutes each. This makes telling the minutes past the hour tricky. To remedy this we can introduce a little minutes circle.<br /><br />This is just a matter of creating a circle outline and a minutes object, and moving them around as<br /><br /><pre class="brush:plain">x: 135*math.sin(math.rad({drh}))<br />y: -135*math.cos(math.rad({drh}))</pre><br />Alternatively, we could use a dial that has a more useful number of minor markers. But the minutes circle is more visually striking, I think.<br /><br /><br /><b><i>Spotlight with Minutes and Date</i> </b><a href="https://www.dropbox.com/s/dvu4k3lcuuo1fnp/spotlight-minutesdate.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-9M0EU7MJPM0/VP4tL_UyjqI/AAAAAAAABbk/pFsFC5vI6Ws/s1600/Screenshot_2015-03-09-23-16-27.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-9M0EU7MJPM0/VP4tL_UyjqI/AAAAAAAABbk/pFsFC5vI6Ws/s1600/Screenshot_2015-03-09-23-16-27.png" height="179" width="200" /></a></div><br />Okay, last one of these. I figured 'centre', above the hour line was the best place to put the date. For this, we have to make sure the date stays on the right side of the line and stays the right way up.<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-YAcm0USj-5U/VP89brel-WI/AAAAAAAABcA/o_3ZZQcz5dU/s1600/date-diagrams.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://1.bp.blogspot.com/-YAcm0USj-5U/VP89brel-WI/AAAAAAAABcA/o_3ZZQcz5dU/s1600/date-diagrams.png" height="200" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Positions of the date relative to the hour line (left) and the corresponding positioning functions (right)</td></tr></tbody></table><br />The code to do this is<br /><br /><pre class="brush:plain">x: -165*math.sin(math.rad({drh})) + ({dh11}<6 and -20 or 20)*math.cos(math.rad({drh}))<br />y: 165*math.cos(math.rad({drh})) + ({dh11}<6 and -20 or 20)*math.sin(math.rad({drh}))<br />Rotation: {drh} + ({dh11}<6 and -90 or 90)</pre><br />Where the first terms for x and y move the date around with the hour line (note the signs), and the second terms are the positioning functions (dx, dy) that shift the date to above the hour line. The conditional in the rotation makes sure the text stays the right way up.<br /><br />There is a slight problem with this on the Moto 360 - as you might notice in the above, the date will dip below the 'flat tire' line between about 11:15 and 12:45. If you have a 360, you might want move the date further along the line (change '165' in the code to something smaller).<br /><br /><br /><br /><b>> Fun with Progress Rings</b><br /><br /><br /><b><i>Circles 1</i> </b><a href="https://www.dropbox.com/s/fmwmf28o110yko9/circles-1.watch?dl=0">[WM]</a><b><br /></b><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-dNpSyJaW1SE/VP-sXsXOckI/AAAAAAAABcQ/e8vSCcgYhTE/s1600/circles1.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://1.bp.blogspot.com/-dNpSyJaW1SE/VP-sXsXOckI/AAAAAAAABcQ/e8vSCcgYhTE/s1600/circles1.png" height="180" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">2:27am (left) and 9:28pm (right)</td></tr></tbody></table>This and the following two designs are all variations on the same theme - it was inspired by an advert, I think. Can't remember what it was an advert for, though.<br /><br />The outer ring segment represents the hours, and fills in (clockwise) towards midday, then 'unfills' from midday to midnight. This is made with a 'Segment Between' shader (WatchMaker Premium only) with<br /><br /><pre class="brush:plain">Degree Start: {dh23} < 12 and 0 or {drh0}<br />Degree End: {dh23} < 12 and {drh0} or 360</pre><br />The orange circles around the outside are hour markers, and also give the hour ring the appearance of rounded corners. In this design, the smaller white circles over the hour markers act as minute markers, which change colour with the passing minutes, as for example<br /><br /><pre class="brush:plain">Colour: {dm}>=5 and 'ffbd2a' or 'fbfbfb'</pre><br />for the 5 minute marker, and so on.<br /><br /><br /><b><i>Circles 2</i> </b><a href="https://www.dropbox.com/s/csmesz3rce799k6/circles-2.watch?dl=0">[WM]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-5gOU6Kjcno0/VPzdq_oSALI/AAAAAAAABZM/_jerBUze40U/s1600/Screenshot_2015-03-08-21-34-04.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-5gOU6Kjcno0/VPzdq_oSALI/AAAAAAAABZM/_jerBUze40U/s1600/Screenshot_2015-03-08-21-34-04.png" height="180" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">9:34pm</td></tr></tbody></table><br />This design has the minutes as a separate inner ring, with four marker that behave similar to above, and with a 'progress ring' which indicates the exact minutes past the hour. The progress ring is made with another Segment shader, and the rounded corners on leading edge is done with a small circle, moved around with sin and -cos.<br /><br /><br /><b><i>Circles 3</i> </b><a href="https://www.dropbox.com/s/f3faj2qac33tao2/circles-3.watch?dl=0">[WM]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-obO4oNa8vDs/VQCy78QmERI/AAAAAAAABcs/FuLdAkqafjI/s1600/circles3-alt.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-obO4oNa8vDs/VQCy78QmERI/AAAAAAAABcs/FuLdAkqafjI/s1600/circles3-alt.png" height="180" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">1:36am (left) and 8:51pm (right)</td></tr></tbody></table>This design is a sort of combination of the previous two, with the minutes progress ring placed on top of the hours ring. The minutes ring is made white when it overlaps an hours ring, and orange otherwise.<br /><br />To make this we start with <i>Circles 1</i>, and for the minutes ring create two Segment Between shaders, which overlap half of the hours ring, with <br /><br /><pre class="brush:plain">Segment 1:<br />Segment Start: 0<br />Segment End: {drm}<{drh0} and {drm} or {drh0}<br />Color: {dh23}<12 and 'fbfbfb' or 'ffbd2a'<br /><br />Segment 2:<br />Segment Start: {drh0}<br />Segment End: {drm}>{drh0} and {drm} or {drh0}<br />Color: {dh23}<12 and 'ffbd2a' or 'fbfbfb'</pre><br />We then need to create a smaller copy of the hours segment from <i>Circles 1</i>, as well as a segment with<br /><br /><pre class="brush:plain">Segment Start: {dh23} < 12 and {drh0} or 0<br />Segment End: {dh23} < 12 and 360 or {drh0}<br />Color: fbfbfb</pre><br />to cover up the centre of the minutes segments and recreate the inner half of the hours ring. <br /><br />The minute markers work as in <i>Circles 1</i>, but this time the hour markers also change colour, as for example,<br /><br /><pre class="brush:plain">Colour: ((({dh11}>1 and {dh23}<12) or ({dh11}<1 and {dh23}>=12)) and {dm}>=5) and 'fbfbfb' or 'ffbd2a'</pre><br />You might notice in the screenshots above that this design seems to have a thin grey shadow/outline around some of the ring. I don't know if this is a bug or a feature of the shaders, but ideally it shouldn't be there.<br /><br /><br /><i><b>Apple Activity</b></i> <a href="https://www.dropbox.com/s/1k4o6ygsspi1gon/apple-activity.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-7eAlLA6IXsY/VP-s8tGKg-I/AAAAAAAABcY/VuNJOQOBq5g/s1600/Screenshot_2015-03-11-02-32-47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-7eAlLA6IXsY/VP-s8tGKg-I/AAAAAAAABcY/VuNJOQOBq5g/s1600/Screenshot_2015-03-11-02-32-47.png" height="180" width="200" /></a></div><br />I saw something that looked like this in an Apple Watch promo - I think it was supposed to be an activity tracker. Anyway, it's a watch face for Android, now (take <i>that</i>, Apple). Shaders for progress rings, circles over the edges for rounded corners, I'm sure you get the idea by now.<br /><br /><br /><b><i>Buffering</i> </b><a href="https://www.dropbox.com/s/1nwjdaf64az1mjs/buffering.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-RXzCwe3GFYo/VPzjCJRQJeI/AAAAAAAABaQ/_n_ZTPYZCTk/s1600/gif_u11.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-RXzCwe3GFYo/VPzjCJRQJeI/AAAAAAAABaQ/_n_ZTPYZCTk/s1600/gif_u11.gif" height="200" width="200" /></a></div><span id="goog_818145882"></span><span id="goog_818145883"></span><br />I was playing with shaders and came up with this, that looks sort of like a loading/buffering ring. The ring fills and unfills once a second, and the point where the ring fills from/to indicates the current seconds past the minute. <br /><br />The code for this is<br /><br /><pre class="brush:plain">Segment Start: ({ds}%2==0) and {drs} or ({drs}+0.36*{dss})%360<br />Segment End: ({ds}%2==1) and {drs} or ({drs}+0.36*{dss})%360</pre><br />The percentage in the image above is the percentage of the day that has passed<br /><br /><pre class="brush:plain">Text: string.format('%.1f', 100*{dtp})</pre><br />The format function is used to round the percentage to one decimal place (<a href="http://www.lua.org/manual/5.3/manual.html#6.4">Lua</a> uses <a href="http://en.wikipedia.org/wiki/Printf_format_string">C-style formatting</a>). Alternatively, I guess you could have hour and minute rings doing similar to this seconds ring, but that might be a bit dizzying.<br /><br /><br /><b>Bonus: <i>How to Make a Battery Ring in Facer</i> </b><a href="https://www.dropbox.com/s/o88ts0v23l5vxtp/Battery%20Circle.face?dl=0">[Facer]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-ehRgXkjmJkU/VPzeGkk3dkI/AAAAAAAABZU/1VtmTRy4zSY/s1600/Screenshot_2015-03-03-21-29-41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-ehRgXkjmJkU/VPzeGkk3dkI/AAAAAAAABZU/1VtmTRy4zSY/s1600/Screenshot_2015-03-03-21-29-41.png" height="180" width="200" /></a></div><br />As we've seen, making a progress ring in WatchMaker Premium is easy with shaders. But, you can actually make something similar to a progress ring in Facer.<br /><br />The easy way to do this is to use polygons (squares, triangles, hexagons) to cover up sections of a ring, with the polygons coloured the same as the face background. We can then alter the polygon opacities to hide/reveal sections of the ring.<br /><br />This gives us a progress ring with discrete intervals. How granular it is will depend on how many polygons we're willing to set up. For a battery meter, we could cover up a ring with 100 little squares, but it'd be a pain in the arse to build.<br /><br />There is also a way to do a continuous progress ring, without <i>too much</i> effort - though it's a bit of an elaborate workaround. First we cover up a ring with 6 triangle, then we can shift, rotate and change the opacities of those triangles to hide/reveal the ring.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-kAGQ3h1BBX8/VPuNHqrMGdI/AAAAAAAABWk/BnPibXYYXeU/s1600/progress-ring.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-kAGQ3h1BBX8/VPuNHqrMGdI/AAAAAAAABWk/BnPibXYYXeU/s1600/progress-ring.png" height="200" width="180" /></a></div>It works sort of like a <a href="https://en.wikipedia.org/wiki/Hand_fan">folding fan</a>. The first triangle moves into the position of triangle 2, then turns transparent. Triangle 2 then moves to 3 and turns transparent. And so on, until we get to triangle 6. Obviously, if we move triangle 6 around the circle like the others, it'll cover up section 1. So instead, we move it vertically upwards.<br /><br />Admittedly, this makes the angle on the leading edge of the ring look a bit off in section 6. But we work with what we've got. There are various fiddly little work-arounds that we could use to correct this - e.g. we could cover the leading edge with a little circle or square. Those are left as an exercise for the reader.<br /><br />The code works like this<br /><br /><pre class="brush:plain">Triangle 1:<br />x: (160+50*cos(rad(3.6*#BLN#))+80*sin(rad(3.6*#BLN#)))<br />y: (160-80*cos(rad(3.6*#BLN#))+50*sin(rad(3.6*#BLN#)))<br />Rotation: (30+3.6*#BLN#)<br />Opacity: $#BLN#<(100/6)?100:0$<br /><br />Triangle 2:<br />x: $#BLN#<(100/6)?260:(160+50*cos(rad(3.6*#BLN#))+80*sin(rad(3.6*#BLN#)))$<br />y: $#BLN#<(100/6)?160:(160-80*cos(rad(3.6*#BLN#))+50*sin(rad(3.6*#BLN#)))$<br />Rotation: $#BLN#<(100/6)?30:(30+3.6*#BLN#)$<br />Opacity: $#BLN#<(200/6)?100:0$</pre><br />And so on for the next 3 triangles (with the boundaries and initial positions suitably adjusted). Finally, for the last triangle, we have<br /><br /><pre class="brush:plain">x: 110<br />y: $#BLN#<(500/6)?80:(80 - 130*(0.06*#BLN# - 5))$<br />Rotation: 30<br />Opacity: $#BLN#<100?100:0$</pre><br />Aside - constructing this made Facer run slooooooow.<br /><br />So there you have it - it's not easy, but it is possible to make a progress ring in Facer. You could do the same trick in WatchMaker (free) if you didn't want to pay for Premium. But personally, I'd prefer to pay - shaders are easier, and more flexible.<br /><br /><br /><br /><b>> Other Assorted Nonsense</b><br /><br /><br /><b><i>50 Shades of Grey</i> </b><a href="https://www.dropbox.com/s/a7z3cy24q3e5s8v/50%20Shades.face?dl=0">[Facer]</a> <a href="https://www.dropbox.com/s/urv3hycz3w1tilm/50-shades.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-wEYc-LKqDs4/VPzjt14H9cI/AAAAAAAABaU/n2ykMj0RrOw/s1600/gif_u20.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-wEYc-LKqDs4/VPzjt14H9cI/AAAAAAAABaU/n2ykMj0RrOw/s1600/gif_u20.gif" height="200" width="200" /></a></div><br />This is the sort of dumb thing I think up when I can't get to sleep. To make this, we create a watchface with a white background, and add a black circle/square that fills the whole screen. We then set the circle's opacity to<br /><br /><pre class="brush:plain">WM: 2*math.floor(5*{dsps}/6000)<br /><br />Facer: (2*floor(#DWFSS#/7.2))</pre><br />This will cycle through 50 different shades of grey - from white to black - over the course of a minute.<br /><br />Alternatively, we could have it cycle from white to black to white, so that we don't have the sudden jump from black to white at the end of a minute - <a href="https://www.dropbox.com/s/k61m0j1b3l3ixjb/50-shades-cycle.watch?dl=0">[download]</a><br /><br />For that, the code is<br /><br /><pre class="brush:plain">WM: {ds}<=30 and 4*math.floor(5*{dsps}/6000) or (198-4*math.floor(5*{dsps}/6000))</pre><br />Aside - Facer doesn't seem to allow mathematical expressions in both the then-statement and the else-statement (?!), so we can't adapt the above. Which is annoying.<br /><br /><br /><b><i>That Dress</i></b> <a href="https://www.dropbox.com/s/xxi7di5kmio4nia/that-dress.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-GFEligVjCxA/VQLwocsMlwI/AAAAAAAABdk/86WjhP8wezo/s1600/gif_u32.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-GFEligVjCxA/VQLwocsMlwI/AAAAAAAABdk/86WjhP8wezo/s1600/gif_u32.gif" height="200" width="200" /></a></div>You know <a href="http://www.wired.com/2015/02/science-one-agrees-color-dress/">the one</a>. This works similarly to the above design - only this time we use '<a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a>' shaders (WatchMaker Premium). For the background, we have a circle that fills the screen with <br /><br /><pre class="brush:plain">Colour: 3228de<br />Saturation: -{dsps}/600</pre><br />And the text, and the bars at the top and bottom of the screen are<br /><br /><pre class="brush:plain">Colour: a07b35<br />Value: 8*({dsps}/6000)-80</pre><br />This will go from black-blue to white-gold over the course of a minute. We can get a similar effect by layering a blue circle on top of a white background, and black text on top of a gold copy of the text, then varying the opacity of the black and blue. That method should work in Facer.<br /><br />Alternatively, we can have the face cycle black-blue to white-gold to black-blue - <a href="https://www.dropbox.com/s/2mphydfq52tmor7/that-dress-cycle.watch?dl=0">[download]</a><br /><br /><pre class="brush:plain">Circle:<br />Saturation: {ds}<=30 and -{dsps}/300 or {dsps}/300-198<br /><br />Text/bars:<br />Value: {ds}<=30 and {dsps}/375-80 or 78-{dsps}/375</pre><br /><br /><b><i>Racing Numbers</i> </b><a href="https://www.dropbox.com/s/pgzw2ect1ku6jw1/racing-numbers.watch?dl=0">[WM]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-eeugEZmXbO0/VPzn71FIu3I/AAAAAAAABag/-zbrKyYFSRc/s1600/Screenshot_2015-03-08-21-30-10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-eeugEZmXbO0/VPzn71FIu3I/AAAAAAAABag/-zbrKyYFSRc/s1600/Screenshot_2015-03-08-21-30-10.png" height="181" width="200" /></a></div><br />Racing, in the sense that the numbers are moving from the right side of the screen to the left, all at different speeds (I couldn't think of a better name). Alternatively, you can think of it as the numbers being positioned horizontally based on their current value. It's straightforward enough to construct, I'm sure you can figure it out.<br /><br /><br /><b><i>Jitters</i></b> <a href="https://www.dropbox.com/s/9gjgflczxogx24y/Jitters.face?dl=0">[Facer]</a><b><i> </i></b><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-jJoBl3rIEGk/VQG8XjG4EuI/AAAAAAAABdA/FhPaZV5fRL0/s1600/Screenshot_2015-03-12-15-57-03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-jJoBl3rIEGk/VQG8XjG4EuI/AAAAAAAABdA/FhPaZV5fRL0/s1600/Screenshot_2015-03-12-15-57-03.png" height="180" width="200" /></a></div><br />I don't have a gif of this - so imagine the time in the above image frantically jittering around the centre of the screen. The background is brown because I was tempted to call it 'too much coffee'.<br /><br />This design is pretty much a case of, I saw that Facer had a 'random' function and I wanted to contrive a use for it. To make this face, we just set the position of the time object to<br /><br /><pre class="brush:plain">x: (150+rand(0,20))<br />y: (170+rand(0,20))</pre><br />The time will be moved with every frame refresh - in Facer's case, I think that's 60fps. As far as I can tell there's no way to slow it down. I imagine this would get annoying after a while.<br /><br /><br /><br /><b>Conclusion</b><br /><br />If you have a smart watch, making the face a replica of a 'dumb' watch seems a bit like missing the point. Having said that, if I saw a replica of my old watch I'd probably download it.<br /><br />But the point is, you can do some design things with a smart watch that you could never do with a dumb watch. These are just a few ideas of some cool things you can do with face making apps. <br /><br />Feel free to download, use, modify, re-post, etc. any of the designs or code in this post. You don't need to ask permission. If you do re-post, a name-check or link back would be nice, but I won't hold you to it.<br /><br />The folder with all the faces is available <a href="https://www.dropbox.com/sh/07oras34brqi3kh/AAASaFy2le10J35DkqepscPGa?dl=0">here</a>.<br /><br />If you have any problems, want more details, or just want help with something, feel free to ask in the comments and I'll see what I can do. <br /><br /><br />Oatzy.<br /><br /><br />[If I had any sense I'd be trying to make some money off these...]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com5tag:blogger.com,1999:blog-14769935.post-25044723355565565182015-02-23T13:51:00.001-08:002015-02-23T13:51:30.869-08:00Designing a General Relativity Based Casual GameLet's suppose, for the sake of arguing, that I'm unemployed. It'd pretty great if there were a way I could make a quick buck (or a quick quid, in my case). Obviously, the solution is to make a super addictive mobile game. Then I can just sit back and watch the cash come pouring in.<br /><br />Okay, so that's a pretty terrible plan - making a mobile app of any kind is no trivial thing, and there's no guarantee of making any significant amount of money (or any money at all). Still, I thought about it, and wondered - if I were to make a game, what would I do. This is one of the ideas I came up with.<br /><br /><br /><b>Game Concept</b><br /><br />A common type of casual game is the <a href="https://en.wikipedia.org/wiki/Game_physics">physics-based game</a> - that is, a game where objects obey a form of classical Newtonian mechanics: your Angry Birds, your Flappy Birds, your games that don't involve birds. So, for example, when you catapult an angry bird, <a href="http://www.wired.com/2010/10/physics-of-angry-birds/">it follows a parabolic path</a> like a real bird would (drag not-withstanding). <br /><br />And that's all fine, but I wondered if you could make a game based on non-classical physics - quantum mechanics, relativity, that sort of thing.<br /><br />Now, I don't want to call this '<a href="http://www.imdb.com/title/tt0816692/">Interstellar</a>: The Game' (not least because of copyright). That is, however, a good short hand for the idea behind the game.<br /><br />The idea is this: you've been off exploring space, and now it's time to go home. So the main objective is to get back to Earth as quickly as possible, whilst dodging obstacles like planets, stars, black holes. But these massive objects have a gravitational pull, which can make maneuvering a little trickier, especially if you get too close to a black hole.<br /><br />On the other hand, you can use massive objects to get a speed boost, by doing for example a <a href="https://en.wikipedia.org/wiki/Oberth_effect">powered flyby</a>. But being close to massive objects for too long (black holes in particular) will cause time dilation, which might make you late home.<br /><br /><br /><b>Game Construction</b><br /><br />The way I imagine the game is as a '<a href="https://en.wikipedia.org/wiki/Side-scrolling_video_game">side-scroller</a>', where you move right to left (assuming the phone/tablet is held in landscape). I figure it would be a <a href="https://en.wikipedia.org/wiki/Video_game_graphics#Top-down_perspective">top-down</a> view, moving in the x-direction with the spaceship - i.e. the sprite would be fixed in the x-direction, with freedom of movement up and down. All the obstacles would then move towards the sprite in the negative x-direction.<br /><br />Here's what a (non-relativistic) binary orbit looks like from a frame of reference moving in the x-direction with the particle (blue).<br /><div style="text-align: center;"><br /><iframe allowfullscreen="" frameborder="0" height="375" mozallowfullscreen="" src="//player.vimeo.com/video/119661162" webkitallowfullscreen="" width="500"></iframe> </div><br />In the code, this is done by calculating the particle velocity as normal, but instead of adding the velocity in the x-direction to the particle, you subtract it from all the obstacles. <br /><br />For the sprite's up-down movement, you could either constrain the player to within the screen, or else if they drift off the edge of the screen, it would be a 'lost in space' game over.<br /><br />Level construction can either be done by hand, or levels can be generated randomly / <a href="https://en.wikipedia.org/wiki/Procedural_generation">procedurally</a> by adding massive objects of varying type/size/mass etc. at various points along the game map. I'd say go for random/procedural, since that makes creating levels easier, and would mean there are effectively unlimited levels. Though it might be worth storing generated levels for replayability.<br /><br />For the representation of black holes, you could show an <a href="https://en.wikipedia.org/wiki/Accretion_disc">accretion disc</a> (<a href="http://www.wired.com/2014/10/astrophysics-interstellar-black-hole/">like they did in Interstellar</a>), have a background star field that's <a href="https://en.wikipedia.org/wiki/Gravitational_lens">gravitationally lensed</a>, or show a dotted line for where the event horizon is. Alternatively, you could do nightmare mode - give no visual indication of a black hole, and let the player infer its presence from its gravitational pull.<br /><br />I tend to think that controls should be kept simple, and should be appropriate to the medium - in other words, no on-screen d-pads on touchscreens (if you can help it). Instead, I'd say use four directional swipes for a speed boost in whichever direction. In terms of code, this would be done by adding some constant to the velocity in the direction of the swipe.<br /><br />One thing to remember is that in space there is no drag - nothing to slow you down (gravity notwithstanding). Steering like this can be surprisingly tricky. If you accelerate left, you will keep moving leftwards until you accelerate right to counter balance.<br /><br />On the other hand, no drag means the player could keep accelerating forward until they're going arbitrarily fast. To stop this, you could put a limit on how many times the player can accelerate - for example, say that there's a limited amount of fuel. This also means the player would have to use their fuel wisely - baring in mind that breaking counts as accelerating. This makes tricks like gravitational assists even more important.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-yPpxVUvVcp0/VOoWP3kmP8I/AAAAAAAABWE/0qH59G3HA-o/s1600/game-mockup-alt1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-yPpxVUvVcp0/VOoWP3kmP8I/AAAAAAAABWE/0qH59G3HA-o/s1600/game-mockup-alt1.png" /></a></div><br />Anyway, having an idea is all well and good, but really an idea isn't worth much if you can't prove that it's viable. So...<br /><br /><br /><b>Game Mechanics</b><br /><br /><i><b>Preamble</b></i><br /><br />I studied theoretical physics at university, and that included a module on General Relativity. But that module only covered the basics. As the lecturer pointed out, General relativity is a graduate level topic.<br /><br />I read a bunch of articles for this blog, and have tried to get as scientifically accurate as possible - or at least I've tried to avoid making massive errors. But even so, there are numerous assumptions, approximations, and inaccuracies in this formulation. So just bear that in mind - I don't necessarily know what I'm talking about. Do feel free to offer corrections.<br /><br /><i><b>Prototyping</b></i> - For coding the prototype/simulations I used Python with <a href="http://pygame.org/">PyGame</a>.<br /><br />In PyGame, coordinates are defined with the origin (0,0) in the top right corner. This isn't a problem mathematically, it just means the graphical layout is 'upside-down'.<br /><br />To make the videos in this post, I added the line<br /><pre class="brush:python">pygame.image.save(screen, 'output/frame%05d.png' % framenum)</pre>to the code's main loop to save individual frames as a series of images (you'll need to iterate '<i>framenum</i>'). I then used '<a href="http://www.andrewnoske.com/wiki/ImageJ">ImageJ</a>' to convert those images to (avi) video.<br /><br /><b><i>Terminology</i></b> - I'm going to refer to moving objects as 'particles', 'planets', or as a 'spaceship' in the context of the game. I'll refer to static, gravitating objects as 'obstacles', '(massive) bodies', or 'stars'. In images and videos, planets are blue, and stars are yellow.<br /><br /><i><b>Units</b></i> - I chose to use 'pixels' as the unit of length, and 'frames' as the unit of time. In this case, velocity is measured in 'pixels per frame' (px/fr).<br /><br />This is useful, because it means the velocity of our particle is updated as \(v(t) = v(t-1)+a(t)\) and the position is updated as \(x(t) = x(t-1)+v(t)\). For my simulation, I used a frame rate of 50fps.<br /><br /><i><b>Physical Constants</b></i> - We could use <a href="https://en.wikipedia.org/wiki/Geometrized_unit_system">geometrised units</a>, where the <a href="https://en.wikipedia.org/wiki/Gravitational_constant">Gravitational Constant</a> (G) and the <a href="https://en.wikipedia.org/wiki/Speed_of_light">Speed of Light</a> (c) are both set equal to one. In that case we wouldn't need to include them in the maths/code. However, my inner-mathematician likes generality, so I'm going to keep them around.<br /><br />The constant 'G' only ever appears alongside obstacle masses (as GM), so we can set it to one and say that it's value is absorbed into the (otherwise arbitrary) mass values. It's worth keeping G around though, so we can easily tweak the strength of gravity, if need be.<br /><br />The speed of light controls the strength of relativistic effects - larger c, weaker relativity. In my code I set the speed of light to c = 12 px/fr. This seems to make things behave in a way that looks 'right'. <br /><br /><b><i>Object Properties</i></b> - We could try to go for a certain level of realism - try, for example, to work out a conversion between real world distances and pixels, try to get everything to scale. But that's too much faffing. Besides, if everything were to scale, a 1px Earth would orbit at a distance of 23000px around a 100px sun.<br /><br />For obstacle masses, I set M = 500 (arbitrary units). The particle mass isn't really important since it can be cancelled out in all the equations. From a theoretical perspective, all that matters is that it's much smaller than the obstacle masses. I set both the obstacles and particle radii to 12.5 pixels (25x25px sprites). Object radii aren't important outside of collision handling.<br /><br /><br /><b>Newtonian Gravity</b><br /><br />For particle acceleration, we start with <a href="https://en.wikipedia.org/wiki/Newton%27s_law_of_universal_gravitation">Classical Newtonian gravity</a>.<br /><br />\[ a = \frac{G M}{r^{2}} \]<br /><br />where G is the gravitational constant, M is the mass of the gravitating body (the obstacle) and r is the distance between the centres of our particle and the obstacle. We're assuming that the particle is moving in a 2D plane, so we have \(r = \sqrt{dx^{2} + dy^{2}}\), where \(dx = x_p - x_{ob}\) and \(dy = y_p - y_{ob}\) are the x and y distances between the particle and obstacle.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-KA7BtodnP1k/VN_Q6kXMNxI/AAAAAAAABT8/jdyQ7bmlQ18/s1600/radial-distance-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-KA7BtodnP1k/VN_Q6kXMNxI/AAAAAAAABT8/jdyQ7bmlQ18/s1600/radial-distance-diagram.png" /></a></div>This gives us the magnitude of the acceleration, pointing from the centre of the particle to the centre of the obstacle. For our purposes, we need to resolve this acceleration into x and y components.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-mmVxm7HKGlA/VN_REE7uvtI/AAAAAAAABUE/a7ry0VTCYRI/s1600/angle-resolve-diagram-alt1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-mmVxm7HKGlA/VN_REE7uvtI/AAAAAAAABUE/a7ry0VTCYRI/s1600/angle-resolve-diagram-alt1.png" height="171" width="400" /></a></div><br />The components are given by<br /><br />\[ \ddot{x} = -\frac{G M}{r^{2}} \cos(\theta) \equiv -\frac{G M}{r^{3}} dx\\ \\<br />\ddot{y} = -\frac{G M}{r^2} \sin(\theta) \equiv -\frac{G M}{r^{3}} dy\]<br /><br />Where \(\theta = \arctan\left(\frac{dy}{dx}\right) \) is the angle between the x-axis and the position/acceleration vector. The <a href="https://en.wikipedia.org/wiki/Notation_for_differentiation#Newton.27s_notation">double dots</a> mean 'second derivative with respect to time', i.e. acceleration.<br /><br />If there are multiple gravitating objects, we have to calculate the contributions from each, and <a href="https://en.wikipedia.org/wiki/Euclidean_vector#Addition_and_subtraction">add them</a> all together to get the total acceleration. <br /><br />\[ \ddot{x} = -\sum_i \frac{G M_{i}}{r_{i}^{3}} dx_{i} \\ \\<br />\ddot{y} = -\sum_i \frac{G M_{i}}{r_{i}^{3}} dy_{i} \]<br /><br />As mentioned above, once we have the acceleration we add that to the velocity, then add the velocity to the position to work out where the particle moves to. Below is an example of a Newtonian orbit using the above.<br /><div style="text-align: center;"><br /><iframe allowfullscreen="" frameborder="0" height="375" mozallowfullscreen="" src="//player.vimeo.com/video/119661161" webkitallowfullscreen="" width="500"></iframe></div><br /><br /><b>General Relativity</b><br /><br />Since we want the game to include <a href="https://en.wikipedia.org/wiki/General_relativity">General Relativistic</a> effects like time dilation, we also have to take into account the other effects of relativity - in particular, how it modifies particle motion/acceleration.<br /><br />For simplicity, we're going to assume our obstacles (planets, stars, black holes) are uncharged, and non-rotating. In that case, we use the <a href="https://en.wikipedia.org/wiki/Schwarzschild_metric">Schwarzchild metric</a>, which describes the gravitational field in the vicinity of an (uncharged, non-rotating) massive object. <br /><br />\[ c^2 d\tau^2 = \left(1 - \frac{r_s}{r}\right) c^2 dt^2 - \frac{dr^2}{\left(1- \frac{r_s}{r}\right)} - r^2 d\theta^2 \] <br /><br />We're assuming the particle is moving in the 2D plane around the equator of the massive object. \(r_s\) is the object's <a href="https://en.wikipedia.org/wiki/Schwarzschild_radius">Schwarzchild radius</a> (also known as the 'event horizon' in the context of black holes), defined as<br /><br />\[ r_s = \frac{2GM}{c^2} \]<br /><br />Without going into too much detail, <a href="http://arxiv.org/pdf/gr-qc/9505010v1.pdf">we can derive</a> from the Schwarzchild metric the particle's acceleration in Cartesian-like coordinates as<br /><br />\[ \ddot{x} = - \frac{G M}{r^3} dx - \frac{3 G M L^2}{c^2 r^5} dx \\ \\<br />\ddot{y} = - \frac{G M}{r^3} dy - \frac{3 G M L^2}{c^2 r^5} dy \]<br /><br />Where the first term is the classical Newtonian gravitation, as seen above. <br /><br />The second term is purely relativistic, and will usually only have a significant effect when a particle is sufficiently close to a massive body's Schwarzchild radius.<br /><br />The main effect of this term is to increase gravitational acceleration in the vicinity of the massive object, and to cause close orbits to precess, as can be seen below<br /><div style="text-align: center;"><br /><iframe allowfullscreen="" frameborder="0" height="375" mozallowfullscreen="" src="//player.vimeo.com/video/119661160" webkitallowfullscreen="" width="500"></iframe></div><br />Notice that the point at which the particle is closest to the 'star' (the <a href="https://en.wikipedia.org/wiki/Perihelion_and_aphelion">perihelion</a>) moves over time. This doesn't happen in the classical limit of Newtonian gravity. In fact, explaining the <a href="https://en.wikipedia.org/wiki/Tests_of_general_relativity#Perihelion_precession_of_Mercury">anomalous precession of Mercury</a> was one of the first pieces of evidence supporting the theory of General relativity.<br /><br />In the acceleration equation, L is the <a href="https://en.wikipedia.org/wiki/Angular_momentum">angular momentum</a> (per unit mass) of our particle, and c is the speed of light. Angular momentum (per unit mass) is calculated as<br /><br />\[ L = \left|r\right| \left|v\right| \sin(\phi) \equiv (dx\ v_y - dy\ v_x) \]<br /><br />where \(\phi\) is the angle between the radial vector (r) and the velocity vector (v)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-kL61GNITIBQ/VN_TN0HpsZI/AAAAAAAABUQ/-mCOcMDPFEM/s1600/L-angle-diagram-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-kL61GNITIBQ/VN_TN0HpsZI/AAAAAAAABUQ/-mCOcMDPFEM/s1600/L-angle-diagram-alt.png" /></a></div><br />The angle can be calculated as<br /><br />\[ \phi = \alpha - \theta = \arctan\left(\frac{v_y}{v_x}\right) - \arctan\left(\frac{dy}{dx}\right) \]<br /><br />where \(\alpha\) is the angle between the velocity vector and the x-axis, and \(\theta\) is the angle between the position vector and the x-axis.<br /><br />In the two body case, angular momentum is a <a href="https://www.youtube.com/watch?v=iWSu6U0Ujs8">constant of motion</a>, so only needs to be calculated once - say, once the particle's initial position and velocity has been set.<br /><br /><i><b>Caveat</b></i> - Strictly speaking, the radial length 'r' in the Schwarzchild metric is not the same as the <a href="https://en.wikipedia.org/wiki/Euclidean_distance">Euclidian distance</a> \(\sqrt{dx^2 + dy^2}\). At least, not when relativistic effects are significant. Rather, r is defined as the circumference of a sphere surrounding the massive body, divided by \(2\pi\). Defining r this way means the length of an interval dr isn't affected by the curving of spacetime near the massive body.<br /><br />By comparison, an observer falling towards a black hole would see lengths stretching longer and longer the closer they got to the black hole event horizon - an observer at a distance r would measure the interval dr to have a length of \( \left( 1 - \frac{r_s}{r} \right)^{-\frac{1}{2}} dr \).<br /><br />All this to say, the particle distances displayed in the game (and the simulation videos) are distances in <a href="https://en.wikipedia.org/wiki/Schwarzschild_coordinates">Schwarzchild coordinates</a>, projected onto a Euclidean plane, not distances as viewed by an observer such as our particle/spaceship. <br /><br /><br /><b>Relativity for Multiple Bodies </b><br /><br />This is where things get dicey. In the General relativistic limit, combining the fields of multiple massive objects is <a href="http://cds.cern.ch/record/427018/files/9912113.pdf">not so straightforward</a>.<br /><br />The lazy way of doing this is to just vector sum the relativistic accelerations, like we did for the Newtonian case. This is problematic, though, because it assumes the particle's angular momentum around each massive body is constant. This is not true.<br /><br />Instead, we can make a simplification - we can sum the Newtonian terms as before, but we'll only consider the relativistic term when we're sufficiently close to any given body. This cut-off is going to be fairly arbitrary. Ideally, we want the 'radius of influence' to be as big as possible, but small enough that the particle can never be within the relativistic limits of more than one body at a time. The main problem here is that the way the game is set up means all the massive bodies are unrealistically close together, so it's hard to make the radius of influence big enough to be ideal. In my code, I chose the cut-off to be \(10 r_s \simeq 70px\).<br /><br />Now, the angular momentum around this close body is still not constant (although, arguably, it may be sufficient to assume it is). The forces from other local bodies can cause <a href="https://en.wikipedia.org/wiki/Torque">torque</a>, which will change the particle's angular momentum.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-ioA5jblEazM/VOjWln78icI/AAAAAAAABV0/V0iU9AIKUUk/s1600/torque-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-ioA5jblEazM/VOjWln78icI/AAAAAAAABV0/V0iU9AIKUUk/s1600/torque-diagram.png" /></a></div>Torque is calculated as<br /><br />\[ \Gamma = \left|r\right| \left|F\right| \sin(\gamma)\ \equiv r_x\ F_y - r_y\ F_x\]<br /><br />where \(\gamma = \theta_1 - \theta_0 = \arctan(\frac{dy_1}{dx_1}) - \arctan(\frac{dy_0}{dx_0}) \) is the angle between the position vector (r) and the force vector (F), and \(\theta_0\) and \(\theta_1\) are the angles between r and the x-axis, and F and the x-axis, respectively.<br /><br />For the force from a gravitating body, the torque can be written out as<br /><br />\[\Gamma = \frac{G M_1 }{r_1^3} \left(dy_0\ dx_1 - dx_0\ dy_1\right) \]<br /><br />We only need to take into account Newtonain gravitation in calculating torque, since we're already assuming the particle is outside the relativistic limit of these other bodies.<br /><br />So once the particle enters a massive body's radius of influence, we calculate it's initial angular momentum, as above. Then, in each time step (for as long as the particle is in the radius of influence), we calculate the total torque from all local bodies, and add that torque to the particle's angular momentum (before calculating the new acceleration).<br /><br />This is a big simplification. But it's good enough, and correct in the limiting case of a single/well isolated body. Especially if the radius of influence can be made sufficiently big.<br /><br />While we're on torque - if our spaceship accelerates (fires its thrusters) while it is close to a massive body, we will need to take into account any torque from that as well<br /><br />\[ \Gamma = dx_0\ a_y - dy_0\ a_x \]<br /><br />Where \(a_x\) and \(a_y\) are the x and y accelerations caused by the thrusters. This is ignoring the details of how a spaceship <a href="https://en.wikipedia.org/wiki/Astronautics">actually maneuvers</a>. If you're interested, you'll have to look into that for yourself.<br /><br />So putting it all together we have<br /><br />\[ \ddot{x} = -\sum_i \frac{G M_i}{r_i^3} dx_i \ - \frac{3 G M_0 L_0^{2}}{c^2 r_0^{5}} dx_0\\ \\<br />\ddot{y} = -\sum_i \frac{G M_i}{r_i^3} dy_i \ - \frac{3 G M_0 L_0^{2}}{c^2 r_0^{5}} dy_0 \]<br /><br />where \(M_0\) is the body whose radius of influence the particle is within (if any), and<br /><br />\[L_0 = L_{0}(t-1) +\sum_{i\ne 0} \frac{G M_i}{r_i^3}(dx_i\ dy_0 - dx_0\ dy_i) + (dx_0\ a_y - dy_0\ a_x)\]<br /><br /><br /><b>Time Dilation</b><br /><br />For a single massive object, the <a href="https://en.wikipedia.org/wiki/Time_dilation">time dilation</a> can be derived from the Schwarzchild metric (see above). Dividing through by \(c^2 d\tau \) and rearranging we get<br /><br />\[ \left(1- \frac{r_s}{r}\right)^2 \left(\frac{dt}{d\tau}\right)^2 = \left(1- \frac{r_s}{r}\right)\left(1 + \frac{r^2 \dot{\theta}^2}{c^2}\right) + \frac{\dot{r}^2}{c^2} \]<br /><br />where the dots mean 'derivative with respect to \(\tau\)'. And since \( \dot{r}^2 + r^2 \dot{\theta}^2 \equiv v_x^2 + v_y^2 = v^2 \), we can re-write and rearrange further to get<br /><br />\[ dt = \frac{d\tau}{\left(1- \frac{r_s}{r}\right)}\sqrt{1- \frac{r_s}{r}\left(1 + \frac{r^2 \dot{\theta}^2}{c^2}\right) + \frac{v^2}{c^2}} \]<br /><br />where dt is the '<a href="https://en.wikipedia.org/wiki/Coordinate_time">coordinate time</a>' - time as measured by an observer at rest, far away from any gravitational fields. For the sake of the game we can say that this represents time as measured on Earth. In reality, the Earths gravitational field does cause it's own time dilation effect. \(d\tau\) is the '<a href="https://en.wikipedia.org/wiki/Proper_time">proper time</a>' - time as measured by a clock on our spaceship.<br /><br />Notice, in the limit of a stationary particle, that is \(\dot{r}=r\dot{\theta}=0\), we get the purely gravitational time dilation<br /><br />\[ dt = \frac{d\tau}{\sqrt{1 - \frac{r_s}{r}}} \] <br /><br />Similarly, in the limit of \( r \gt\gt r_s\) (i.e. when our particle is far from any gravitating bodies), the time dilation equation reduces to the <a href="https://en.wikipedia.org/wiki/Special_relativity">Special Relativistic</a> case<br /><br />\[ dt = d\tau \sqrt{1 + \frac{v^2}{c^2}} \]<br /><br />Note - in this, the velocity v is the '<a href="https://en.wikipedia.org/wiki/Proper_velocity">proper velocity</a>' - velocity with respect to proper time. This is different from coordinate velocity - velocity with respect to coordinate time.<br /><br />While a particle can't have a coordinate velocity greater than the speed of light 'c', because of time dilation it can have a proper velocity greater than 'c'. This doesn't, however, mean that a particle can travel faster than light, since light has a proper velocity of infinity. Coordinate velocity and proper velocity are related by<br /><br />\[ v_c = v \left(\frac{d\tau}{dt}\right) \equiv \frac{v}{ \sqrt{1+\frac{v^2}{c^2}}}\]<br /><br />where the equivalence is true in the Special relativistic limit. Notice that when proper velocity equals the speed of light (c), the coordinate velocity equals \(\frac{c}{\sqrt{2}}\) - less than the speed of light. <br /><br /><br /><b>Time Dilation for Multiple Bodies</b><br /><br />Here, we once again have the problem of combining the effects of multiple gravitating masses. Some would argue, at this point, that trying to be scientifically accurate is more trouble than it's worth. Still, I'm going to at least try.<br /><br />To start, we <a href="http://www.ipgp.fr/~tarantola/Files/Professional/GPS/Neil_Ashby_Relativity_GPS.pdf">can</a> <a href="https://en.wikipedia.org/wiki/Time_dilation#Time_dilation_due_to_gravitation_and_motion_together">replace</a> the Schwarzchild radius terms with the local (Newtonian) gravitational potentials as<br /><br />\[ \frac{r_s}{r} = \frac{2GM}{c^2 r} \rightarrow \sum_i \frac{2 G M_i}{c^2 r_i} = \sum_i \frac{2U_i}{c^2} = \frac{2U}{c^2}\]<br /><br />Where \(U_i\) are the individual <a href="https://en.wikipedia.org/wiki/Gravitational_potential">gravitational potentials</a> of the various local gravitating bodies.<br /><br />For the term in \(r^2 \dot{\theta}^2\), we note that \(r^2 \dot{\theta}^2\ = \frac{L^2}{r^2} \), where L is angular momentum (per unit mass). So we can use the same trick we did for combining accelerations - that is, only take this term into account when the particle is within a body's radius of influence.<br /><br />So putting it all together, we have<br /><br />\[ dt = \frac{d\tau}{\left(1 - \frac{2U}{c^2}\right)}\sqrt{1 - \frac{2U}{c^2} - \frac{2U_0}{c^2}\frac{L_0^2}{r_0^2 c^2} + \frac{v^2}{c^2}} \]<br /><br />With \(L_0\) defined as above. Alternatively, we could just omit the angular momentum term since it's of order \(c^{-4}\). In my simulation, it amounted to a ~0.17% decrease in the dilation ratio.<br /><br />The typical dilation ratio in my simulation was \(\frac{dt}{d\tau} \approx 1.07\), which admittedly isn't significant.<br /><br /><a href="http://iopscience.iop.org/0264-9381/32/6/065001/article">In the movie</a> Interstellar, a lot of the time dilation came from the fact that their black hole (Gargantua) was spinning very fast. If you want to do that, you'll have to work it out for yourself - for that you would use the <a href="https://en.wikipedia.org/wiki/Kerr_metric">Kerr metric</a>. With rotating black holes, you'd also need to take into account other effects like <a href="https://en.wikipedia.org/wiki/Frame-dragging">frame dragging</a>.<br /><br />Of course, we've chosen arbitrary masses, an arbitrary speed of light, distances not to any sort of realistic scale. We can always tweak the specific time dilation to our liking. In particular, if the realistic dilation isn't dramatic enough, we could artificially inflate it. So long as we keep key behaviours, like dilation going to infinity when the particle reaches a black hole event horizon, etc.<br /><br /><br /><b>Collision Handling</b><br /><br /><a href="https://en.wikipedia.org/wiki/Collision_detection">Collision handling</a> is important for figuring out when the game is over - for figuring out if the player crashed into a planet, or fell into a black hole. Now, PyGame has <a href="http://www.pygame.org/docs/ref/sprite.html">in-build collision handling</a>. But where's the fun in that. <br /><br />The easiest way to check if two circular objects have collided is to check if the distance between their centres is less than the sum of their radii<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-smCRVtgwpF0/VN_UsVwE3UI/AAAAAAAABUc/4jJAA6aLrTk/s1600/overlap-diagram-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-smCRVtgwpF0/VN_UsVwE3UI/AAAAAAAABUc/4jJAA6aLrTk/s1600/overlap-diagram-alt.png" /></a></div><br />In other words, we have the condition - a collision occurred if \( dx^2 + dy^2 \le (r_p + r_{ob})^2 \).<br /><br />Easy. There is a special case though - because the particle moves in discrete steps from frame to frame, if the particle is moving fast enough, it could move from one side of an obstacle to the other without ever overlapping.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-myRd6udQ_ok/VN_U9ReOJ4I/AAAAAAAABUk/NPvzXIFnMMU/s1600/pass-collision-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-myRd6udQ_ok/VN_U9ReOJ4I/AAAAAAAABUk/NPvzXIFnMMU/s1600/pass-collision-diagram.png" /></a></div><br />The basic collision handling would miss this. So this is a little trickier to catch. In the above diagram, I've traced the path going from the particle's position in the previous step to its current position.<br /><br />We can say that the particle collided with the obstacle if there's a point on that path that's closer to the centre of the obstacle than \( R = r_p + r_{ob} \). In other words, if the particle had moved continuously along the path from \(x_0\) to \(x_1\), would there have been any point(s) where the particle and the obstacle overlapped.<br /><br />To figure this out, first we find the equation for the line passing through the particles' two positions as <br /><br />\[ y = m x + c = \left(\frac{v_y}{v_x}\right) x + \left(y_0 - \frac{v_y}{v_x} x_0 \right) \]<br /><br />(in this case, m and c are gradient and constant, respectively, not mass and speed of light).<br /><br />Second, we're going to define a function for the distance between a point on the particle's path and the obstacle's centre<br /><br />\[ f(x) = (x_{ob} - x)^2 + (y_{ob} - y)^2 = (x_{ob} - x)^2 + (y_{ob} - m x - c)^2 \]<br /><br />Now we're going to look for the point along the path of closest approach to the obstacle. To do that we look for x such that the separation f(x) is minimised. In other words \( \frac{d}{dx} f(x) = 0\)<br /><br />If you work through the maths, you get<br /><br />\[ x_{min} = \frac{x_{ob} + m y_{ob} - m c}{1 + m^2} \]<br /><br />Which gives us the condition - a collision occurred if \(f(x_{min}) \le R^2\) and \(x_0 \le x_{min} \le x_1\) (assuming \(x_0 \le x_1\)).<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-eHDJuUz1uUo/VN_VQYpDHsI/AAAAAAAABUw/kTL9A0yteJE/s1600/pass-collision-diagram-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-eHDJuUz1uUo/VN_VQYpDHsI/AAAAAAAABUw/kTL9A0yteJE/s1600/pass-collision-diagram-2.png" /></a></div>Both these collision handlers can also be used to check if the particle crosses a black hole's event horizon - just replace \(r_{ob}\) with \(r_s\), the Schwarzchild radius.<br /><br />Of course, in the game, the spaceship sprite wouldn't be circular. But, you know. You could create an 'imaginary' circle around the sprite, or give the sprite a radius of zero (so it collides when its centre overlaps the obstacle). Or you could just use a third party library. Either way.<br /><br /><br /><b>Bonus: Elastic Recoil</b><br /><br />For my own amusement, I had the particle <a href="https://en.wikipedia.org/wiki/Elastic_collision">elastically recoil</a> off of the obstacles, as well as the sides of the screen, so that I could just watch it drifting and bouncing about endlessly. It's weirdly hypnotic.<br /><br />For screen boundaries, you have, for example <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-LdC0pGzX7u8/VN_V_E2xlsI/AAAAAAAABU8/rjoJdtiOaiY/s1600/boundry-recoil-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-LdC0pGzX7u8/VN_V_E2xlsI/AAAAAAAABU8/rjoJdtiOaiY/s1600/boundry-recoil-diagram.png" /></a></div>In this case, we flip the particle velocity in the x-direction as \( v_{x}' = -v_{x} \), and move the particle's x position to \( x_{p}' = W - (x_{p} - W) = 2 W - x_p \), where W is the pixel width of the game window. You do the same sort of thing for the other 3 boundaries.<br /><br />For recoil off obstacles, things are not so straight forward. The procedure works like this: First, check for a collision. Then, find the point along the particle's path where it first makes contact with the obstacle - that is where \(f(x) = R^2\).<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-io4XXqZaAsY/VN_WdCOIKzI/AAAAAAAABVA/7rHDmlCpKLU/s1600/contact-diagram-alt1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-io4XXqZaAsY/VN_WdCOIKzI/AAAAAAAABVA/7rHDmlCpKLU/s1600/contact-diagram-alt1.png" /></a></div>Finding this point is just a matter of solving a quadratic equation to get<br /><br />\[ \begin{align*}<br />x' &= \frac{x_{ob} + m y_{ob}-mc}{1 + m^2} \pm \frac{\sqrt{(x_{ob} + m y_{ob}-mc)^2 - (1+m^2)(x^2_{ob} + y^2_{ob}+c^2 - 2 c y_{ob} - R^2)}}{1 + m^2} \\ \\ &\equiv a \pm b<br />\end{align*} \]<br /><br />If \(v_x \gt 0\) then x' = a - b, if \(v_x \lt 0\) then x' = a + b<br /><br />So move the particle back to the point of first contact. Then we want to flip the particle velocity so that the angle of incidence \(\phi\) between the velocity vector (v) and the position vector (r) equals the angle of reflection<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-OxVc9C7cffQ/VN_Wzr05eOI/AAAAAAAABVI/EPh1UZ7DZtE/s1600/elastic-recoil-diagram-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-OxVc9C7cffQ/VN_Wzr05eOI/AAAAAAAABVI/EPh1UZ7DZtE/s1600/elastic-recoil-diagram-alt.png" /></a></div>The new velocity is given by<br /><br />\[v'_x = -\left|v\right| \cos(\beta) \\ \\ v'_y = -\left|v\right| \sin(\beta)\]<br /><br />with \(|v| = \sqrt{v_{x}^{2} + v_{y}^{2}} \), and <br /><br />\[ \beta = \alpha - 2 \phi = 2\arctan\left(\frac{-dy}{-dx}\right) - \arctan\left(\frac{v_y}{v_x}\right) \]<br /><br />where \( \alpha \) is the angle between the x-axis and the old velocity vector (v), and \(\beta\) if the angle between the x-axis and the new velocity vector (v').<br /><br />Note - in the code I used <a href="http://docs.scipy.org/doc/numpy/reference/generated/numpy.arctan2.html#numpy.arctan2">'arctan2(y,x)</a>' from the Numpy library, for which the signs of 'x' and 'y' are important. dy and dx are negative in the above because I have the position vector pointing from the particle to the obstacle (opposite to how it's defined).<br /><br />Finally, we want to move the particle to where it should be from recoiling.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-1L4w2387-As/VN_XudpZf2I/AAAAAAAABVU/bpwJMrTEhK0/s1600/recoil-position-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-1L4w2387-As/VN_XudpZf2I/AAAAAAAABVU/bpwJMrTEhK0/s1600/recoil-position-diagram.png" /></a></div><br />In other words, we want to move the particle to the point along it's new velocity vector, such that it's the same distance from the contact point as it would be if the particle hadn't collided. To do this, we can just resolve the distance \( r' = \sqrt{(x_1 - x')^2 + (y_1 - y')^2} \) in the direction of the new velocity vector, as<br /><br />\[ x_1 ' = x' - r' \cos(\beta) \\ \\ y_1 ' = y' - r' \sin(\beta) \]<br /><br />Admittedly, this doesn't work perfectly - sometimes it behaves a little screwy. Especially when the particle spirals in on an obstacle. Of course, none of this recoil stuff is necessary for the game, since you want collisions to end the game. Like I said, this was more for my own amusement.<br /><div style="text-align: center;"><iframe allowfullscreen="" frameborder="0" height="375" mozallowfullscreen="" src="//player.vimeo.com/video/120176087" webkitallowfullscreen="" width="500"></iframe> </div><br /><br /><b>Conclusion</b><br /><br />If you were feeling really ambitious, you could do the game from the first-person perspective of someone on the spaceship - see for example '<a href="http://jila.colorado.edu/~ajsh/insidebh/index.html">Falling into a Black Hole</a>', or the <a href="http://arxiv.org/find/gr-qc/1/au:+James_O/0/1/0/all/0/1">Interstellar papers</a>.<br /><br />There are almost certainly things wrong with this 'formulation' of General Relativity. But then, this is meant to be for a game, so it <a href="https://en.wikipedia.org/wiki/Rationalization_%28making_excuses%29">doesn't need to</a> be 100% scientifically accurate. I did feel like I should make an effort, though. And I'm willing to call this good enough. If you're someone who knows what they're talking about, feel free to offer your thoughts/corrections.<br /><br />Anyway, I've done my job as a theoretician - now it's up to someone else to actually make the game. If you do, please send me link to the completed game. And maybe give me a name-check? A little kickback would be nice too... ;)<br /><br />You can have a look at my prototype/simulation code <a href="https://www.dropbox.com/sh/l4ai8vacc921x9t/AAAp630npPaluD8QhYNhXbf4a?dl=0">here</a>.<br /><br /><br />Oatzy.<br /><br /><br />[Gravity, don't mean too much to me<a href="http://www.youtube.com/watch?v=seFu9fQ_-FI">.</a>]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com1tag:blogger.com,1999:blog-14769935.post-79390857581030227342015-01-11T16:55:00.001-08:002015-03-14T15:38:48.874-07:00Follow Up: A Binary Proof and a Trinary ClockIn the <a href="http://oatzy.blogspot.co.uk/2015/01/metric-and-binary-clocks-for-android.html">last blog</a> I talked about making a binary clock widget (for Android) using <a href="https://play.google.com/store/apps/details?id=org.zooper.zwfree&hl=en_GB">Zooper</a>, and I made a claim about how individual binary digits can be extracted from a decimal number - what follows is an explanation/proof of why it works.<br /><br /><br /><b>The Claim</b><br /><br />The n-th digit of the binary representation of a number, x, is a zero if \(x \bmod 2^{n+1} \lt 2^{n}\), or a one otherwise.<br /><br /><b>Proof</b><br /><br />To start, we express our number x as a sum of powers of 2. This makes sense, since this is basically how binary works. So we have<br /><br />\[ x = \sum_{i=0} a_{i} 2^{i}\]<br />where the \(a_i\) equal either 1 or 0 - these are equivalent to the i-th digits in the binary form of the number (reading right to left). For example, if x=5 then \(a_0=1, a_1=0, a_2=1\) and all other \(a_i=0\) (since 5 in binary is 101).<br /><br />So lets split the sum up into three parts and expand<br /><br />\[\begin{align*}<br />x &=\ a_{n} 2^{n} \ +\ \sum_{i=0}^{n-1} a_{i}2^{i} \ +\ \sum_{i=n+1} a_{i} 2^{i} \\ \\<br />&= a_{n} 2^{n} \\<br />&+ (a_{0}2^{0} + a_{1}2^1 + \ldots + a_{n-1}2^{n-1}) \\<br />&+ (a_{n+1}2^{n+1} + a_{n+2} 2^{n+2}+ a_{n+3} 2^{n+3}+\ldots)<br />\end{align*}\]<br />Notice that in the second set of brackets, all the terms are divisible by \(2^{n+1}\) - so if we take mod \(2^{n+1}\), all of those terms disappear<br /><br />\[ x \bmod 2^{n+1} = a_{n} 2^{n} + (a_{0}2^{0} + a_{1}2^1 + \ldots + a_{n-1}2^{n-1}) \]<br />The terms in the remaining set of brackets <a href="https://en.wikipedia.org/wiki/Geometric_series">sum to</a> some value between 0 and \(2^{n}-1\) (depending on the values of the \(a_i\)). We'll call this \(\sigma\) for convenience.<br /><br />Therefore, if \(a_n = 1\) then <br /><br />\[ x \bmod 2^{n+1} = 2^{n} + \sigma \ge 2^{n}\]<br />And if \(a_n = 0\) then <br /><br />\[ x \bmod 2^{n+1} = \sigma \le (2^{n} - 1) \lt 2^{n}\]<br />QED<br /><br /><br /><b>Generalisation</b><br /><br />As with powers of 2, we can write numbers as the sum of powers of any number/base. For example, we can write 11 as \(2\cdot 3^0 + 0\cdot 3^1 + 1\cdot 3^2\) - or to put it another way, 11 in base 3 is 102.<br /><br />So in general, we can write a number 'x' in terms some base 'b' as<br /><br />\[ x = \sum_{i=0} a_{i} b^{i}\]<br />where the \(a_i\) are whole numbers between 0 and (b-1) - equivalent to the i-th digits of x in base 'b'. So for example, for x=11 and b=3 we'd have \(a_0 = 2, a_1 = 0, a_2 = 1\) and \(a_i=0\) for all other i.<br /><br />As before, we can split the summation and expand. But this time we're going to divide through by \(b^n\) as well<br /><br />\[\begin{align*}<br />\frac{x}{b^n} &=\ a_{n} \ +\ \frac{1}{b^n}\sum_{i=0}^{n-1} a_{i}b^{i} \ +\ \frac{1}{b^n}\sum_{i=n+1} a_{i} b^{i} \\ \\<br />&= a_{n} \\<br />&+ \frac{1}{b^n}(a_{0}b^{0} + a_{1}b^1 + \ldots + a_{n-1}b^{n-1}) \\<br />&+ (a_{n+1}b^{1} + a_{n+2} b^{2} + a_{n+3} b^{3}+\ldots)<br />\end{align*}\]<br />And now, all the terms in the last bracket are divisible by just 'b', so taking a modulo of 'b' will make those terms disappear. So we have <br /><br />\[ \frac{x}{b^n} \bmod b = a_n + \varepsilon \] <br />where \(\varepsilon \le \frac{b^{n}-1}{b^{n}} \lt 1 \).<br /><br />And from this, we can define a general function for extracting \(a_n\) - the n-th digit of x in base 'b' - as <br /><br />\[ D^{n}_{b}(x) = \left \lfloor{ \frac{x}{b^{n}} \bmod b }\right \rfloor\]<br />where the brackets mean 'floor', or round down to the nearest whole number - basically, get rid of \(\varepsilon\).<br /><br />So as an example, the zeroth and first digits of 35 in base 16 (hexadecimal) are<br /><br />\[ D_{16}^{0}(35) = \left \lfloor{ \frac{35}{16^{0}} \bmod 16 }\right \rfloor = \left \lfloor{ 35 \bmod 16 }\right \rfloor = 3 \\ D_{16}^{1}(35) = \left \lfloor{ \frac{35}{16^{1}} \bmod 16 }\right \rfloor = \left \lfloor{ 2.1875 \bmod 16 }\right \rfloor = 2\]<br />So 35 in hexadecimal is 23. Easy.<br /><br /><br /><b>Trinary Clock </b><a href="https://www.dropbox.com/s/xfxtzcqxtsxs164/trinary-clock.zw?dl=0">[download]</a><br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-OlVcFcleaaE/VLA2jCh2BrI/AAAAAAAABTY/1eEEa8hIvyA/s1600/Screenshot_2015-01-05-15-52-58.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://2.bp.blogspot.com/-OlVcFcleaaE/VLA2jCh2BrI/AAAAAAAABTY/1eEEa8hIvyA/s1600/Screenshot_2015-01-05-15-52-58.png" height="199" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">15:52</td></tr></tbody></table><br />The thing is, binary clocks are great and all. But they're old hat. So now we have a way of finding the individual digits of a number in any base, we can make something a little more unique - a trinary clock.<br /><br /><a href="http://oatzy.blogspot.co.uk/2015/01/metric-and-binary-clocks-for-android.html">Before</a>, when I constructed the binary clock, I used rectangles for each binary digit, which changed colour depending on whether the digit it represented was a one or a zero. For a trinary clock we'd need each rectangle to have 3 state - 0, 1, 2. The problem is, Zooper can only do 2-state logic - if-then-else - not else-ifs, and no nested if-statements. So we can't make a rectangle that switches between 3 colours.<br /><br />Instead, I made each segment a progress bar with values 0-2. For the current value of each bar/digit, we can use the formula we found above - \(D^{n}_{3}(x) = \frac{x}{3^n} \bmod 3\).<br /><br />For example, for the 3rd segment (n=2) of the hours ring, we'd set the current value to<br /><br /><pre class="brush: plain">$(#DH#/9) % 3$</pre><br />The progress bar automatically rounds values down to the nearest whole number, so we don't have to worry about getting rid of any decimals. But you could add a 'floor' function if you wanted. <br /><br />Like the binary clock, I made the sizes of each segment proportional to the values they represent - the three-segment is 3 times bigger than the one-segment, etc. And to make reading clearer, I've made the leading edge of each segment a darker blue - that way it's easier to tell where one segment ends and the next one begins. <br /><br /><br /><b>Quinary (Base 5) Clock </b><a href="https://www.dropbox.com/s/qf8rpja23i334c7/quintary-clock.zw?dl=0">[download]</a><br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-Z_TeN5fNBAA/VLA1OKaN1WI/AAAAAAAABTM/hq3bvehdo-Q/s1600/Screenshot_2015-01-05-22-44-54.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-Z_TeN5fNBAA/VLA1OKaN1WI/AAAAAAAABTM/hq3bvehdo-Q/s1600/Screenshot_2015-01-05-22-44-54.png" height="200" width="199" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">22:44</td></tr></tbody></table><br />Once you've figured out the trinary clock, adapting it to other bases is very straightforward.<br /><br /><br /><b>Decary (Base 10) Clock </b><a href="https://www.dropbox.com/s/q181cj6qh8vxvk9/decary-clock.zw?dl=0">[download]</a><b></b><br /><b><br /></b><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-Z__5e__rY2A/VLAY3U4FuOI/AAAAAAAABS8/qLYZzAJCAuA/s1600/Screenshot_2015-01-06-22-16-19.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://2.bp.blogspot.com/-Z__5e__rY2A/VLAY3U4FuOI/AAAAAAAABS8/qLYZzAJCAuA/s1600/Screenshot_2015-01-06-22-16-19.png" height="200" width="198" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">22:16</td></tr></tbody></table><br />You get the idea...<br /><br /><br /><b>Finally</b><br /><br />As far as telling the time goes, all the clocks in this and the previous blog are pretty... challenging. At least until you get the hang of it. But they look cool. And that, I think is worth the extra effort. If I ever get a smartwatch I'll probably try to port some of these designs over. And if/when I do, you can bet I'll post them here.<br /><br />[edit] - Android Wear versions are <a href="http://oatzy.blogspot.co.uk/2015/03/watch-face-concepts-for-android-wear.html">here</a>.<br /><br /><br />Oatzy.<br /><br /><br />[Decary? Really..?]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com3tag:blogger.com,1999:blog-14769935.post-88632415276273710682015-01-09T09:01:00.000-08:002015-01-11T16:57:50.352-08:00Metric and Binary Clocks (for Android)A long while back I wrote a couple of blogs about clocks. In <a href="http://oatzy.blogspot.co.uk/2010/08/decimalising-time.html">the first</a> I talked about decimalising time. Half a year later, I wrote <a href="http://oatzy.blogspot.co.uk/2011/01/needlessly-incomprehensible-clock.html">another</a> where I designed a couple of circular binary clocks. I thought I'd try to recreate those clocks for my phone.<br /><br />I had a widget making app - <a href="https://play.google.com/store/apps/details?id=org.zooper.zwfree&hl=en_GB">Zooper</a> - installed on my phone, that I'd played with before. I build myself an autumnal homescreen, that looked like this.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-1ptN_E0sBZE/VK22Ojbr9aI/AAAAAAAABRg/2a_c9eleKbo/s1600/Screenshot_2013-10-12-17-13-30.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-1ptN_E0sBZE/VK22Ojbr9aI/AAAAAAAABRg/2a_c9eleKbo/s1600/Screenshot_2013-10-12-17-13-30.png" height="320" width="180" /></a></div>I don't use that theme anymore. These days I prefer a more minimalist homescreen - no widgets, as few icons as possible. But if you like it, you can <a href="https://www.dropbox.com/sh/h0xeohfu4lxoyuh/AAAbtXxZelo8YQSTbEAKa2gza?dl=0">[download]</a> the widgets/wallpaper for the above theme. You'll need the <a href="https://play.google.com/store/apps/details?id=com.batescorp.notificationmediacontrols.alpha&hl=en_GB">Media Utilities</a> app for the music bar.<br /><br />Note - if you want to use any of the widgets I've linked in this blog, you'll need the <a href="https://play.google.com/store/apps/details?id=org.zooper.zwpro&hl=en_GB">paid version</a> of Zooper. Alternatively, you can try to recreate them yourself - I'll include important details in this blog. There are other widget making apps, but I don't know much of using them.<br /><br /><br /><b>How to Make a Basic Clock Widget</b><br /><br />I'm going to quickly go over making a normal clock first so you can get a sense of how stuff works.<br /><br />Making a circular clock, such as below, is pretty straightforward. Create a progress bar object for the minutes (the inner ring) and set its 'curve' value to 360. Then set the min and max values to 0 and 60 respectively and set the current value to #Dm# (the current minutes). You may need to rotate and reposition it.<br /><br />Create the hours circle in much the same way - using #Dh# for 12h format, or #DH# for 24h format. There are then loads of style settings you can play with. In the end you'll have something like this.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-CqeGdG0Yd5g/VK3hzQJm0MI/AAAAAAAABRw/wiLxOFcZIKM/s1600/Screenshot_2015-01-07-19-36-25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-CqeGdG0Yd5g/VK3hzQJm0MI/AAAAAAAABRw/wiLxOFcZIKM/s1600/Screenshot_2015-01-07-19-36-25.png" height="199" width="200" /></a></div>Above is actually one of the built-in widgets that I de-cluttered a little. So if you don't want to go to the trouble of creating a widget from scratch, you can just tweak the ones that are already installed.<br /><br />Note - all the clock images in this blog are shown on a grey background for clarity. Their actual backgrounds (if you download them) are transparent.<br /><br /><br /><b>Backwards Clock </b><a href="https://www.dropbox.com/s/uf5rakmombc5u1c/backwards-clock.zw?dl=0">[download]</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-hQyrHy-wv8Y/VK3iUAeX7HI/AAAAAAAABR4/r_FJRrrezBY/s1600/Screenshot_2015-01-07-17-54-47.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-hQyrHy-wv8Y/VK3iUAeX7HI/AAAAAAAABR4/r_FJRrrezBY/s1600/Screenshot_2015-01-07-17-54-47.png" height="200" width="196" /></a></div><br />This is actually the last one I made. I have an analogue backwards clock on my bedroom wall, that my sister got me from the <a href="http://www.sciencemuseum.org.uk/">Science Museum in London</a>. I figured, while I was at making clocks, why not make one of those as well. I put it second in this blogs because it's trivially different from the normal, forwards clock.<br /><br />As before, you make your hour and minute rings from progress bars, but in this case you set the curve to negative 360. Like I said, trivial.<br /><br /><br /><b>Decimalised/Metric Clock </b><a href="https://www.dropbox.com/s/9ze3ompo7awmuc5/decimal-clock.zw?dl=0">[download]</a><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-icK-w1XDn3E/VK3iqn_2nWI/AAAAAAAABSA/O1zsKda4TIA/s1600/Screenshot_2015-01-07-17-55-56.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://1.bp.blogspot.com/-icK-w1XDn3E/VK3iqn_2nWI/AAAAAAAABSA/O1zsKda4TIA/s1600/Screenshot_2015-01-07-17-55-56.png" height="200" width="193" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">That's 17:55 in real money.</td></tr></tbody></table>The idea behind 'metric' or decimalised time is that the day is redefined as being 10 hours long, with 100 minutes per hour and 100 seconds per minute. The construction of the clock itself is the same as for the normal clock, we just have to convert the current time to metric.<br /><br />For that, we first convert the current time to minutes, and divide that by the total number of minutes in a standard day (24*60) - this gives us the fraction of the day that's elapsed. Multiplying that by the number of hours in a metric day (10) gives us the current decimalised hour.<br /><br />For example, if we decimalise the time 12:51, we get the hour as 5.3542. In fact, because of the way metric time works, 5, is the hour and the first two digits after the decimal point, 35, is the minutes. <br /><br />So for the hours ring, we set the current value to<br /><br /><pre class="brush: plain">$(0.00649*(60*#DH# + #Dm#))$</pre><br />The progress bar object will automatically round this down to the nearest whole number.<br /><br />To extract the minutes, we can use the '<a href="http://en.wikipedia.org/wiki/Modular_arithmetic">modulo</a>' operator (written as a percentage symbol '%' in coding). The modulo operator gives us the remainder from division. So if we multiply the (metric) hours by 100, and take mod 100 we get something like \((100\cdot 5.3542) \bmod 100 = (535.42 \bmod 100) = 35.42\). Again, the progress bar will round this down.<br /><br />So we can set the current value for the minutes ring to<br /><br /><pre class="brush: plain">$(0.649*(60*#DH# + #Dm#) % 100)$</pre><br />I also included a text output the decimalised time. As mentioned above, we can just calculate the metric hour, which will give us something like 5.35. But I wanted it to have it to have the 5:35 format like a normal clock. <br /><br />We already have our expressions above for the metric hours and minutes. We just need to slip in a couple of 'floor' functions to round them down to whole numbers. The other thing is that when the minutes is less than 10, they display as a one digit number, e.g. 5:3 instead of 5:03. To fix this we just throw in an if-statement.<br /><br />The code for the text output of the metric time looks like this<br /><br /><pre class="brush: text">$(floor(0.00694*(60*#DH#+#Dm#)))$:$(0.694*(60*#DH#+#Dm#)%100)<10?(0)$$(floor(0.694*(60*#DH#+#Dm#)%100))$</pre><br />Yeah, it's a little unwieldy.<br /><br /><br /><b>Binary Clock </b><a href="https://www.dropbox.com/s/b0q1du0n4ml66hg/binary-clock.zw?dl=0">[download]</a><br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-yM32TGrtLEM/VK3kQlWezxI/AAAAAAAABSM/hiCBV4seO5M/s1600/Screenshot_2015-01-07-19-29-25.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://2.bp.blogspot.com/-yM32TGrtLEM/VK3kQlWezxI/AAAAAAAABSM/hiCBV4seO5M/s1600/Screenshot_2015-01-07-19-29-25.png" height="199" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><a href="https://en.wikipedia.org/wiki/Binary_number">10011:011101</a> = 19:29</td></tr></tbody></table>In this design, each segment on each ring represents a power of two/binary digit. When the segment is grey it's a zero, when it's blue it's a one. So in the above, the hour (outer ring) is 10011 in binary, or \(1\cdot 16 + 0\cdot 8 +0\cdot 4 + 1\cdot 2 + 1\cdot 1 = 19\), in base 10.<br /><br />In this case, we can't use progress bars. Instead, we have to make each ring segment an individual rectangle object, that will change its colour depending on whether it's representing a one or a zero. This makes constructing the clock a little less straightforward.<br /><br />For the minutes ring, create 6 rectangles each with a curve of 60, and rotated so that they form a circle. Similarly, for the hours ring create 5 rectangles with curve 72, and so on.<br /><br />I originally tried aligning everything 'free-hand'. That didn't work so well, so I created a temporary full circle as a guide. Of course, you don't have to make a binary clock circular - you could have it as a line of dot/bars. I just like the circle design. <br /><br />Now, we need a way of converting the time to binary, and a way of telling each block whether the binary digit it represents is a one or a zero. At this point, I'm going to make a claim -<br /><br /><blockquote class="tr_bq"><i>The n-th digit of a number, x, in binary is a zero if \(x \bmod 2^{n+1} \lt 2^{n}\), or a one otherwise.</i></blockquote><br />I'll prove this in a supplemental blog, that will be posted shortly after this one. In the meantime, you might like to try proving it for yourself.*<br /><br /><i>[edit]</i> - You can read that supplemental blog <a href="http://oatzy.blogspot.co.uk/2015/01/follow-up-binary-proof-and-trinary-clock.html">here</a>. <br /><br />So we can use this condition to set/change the colours of each rectangle. For example, for the 2nd rectangle on the minutes ring (n=1), go to "Advanced Parameters", and enter something like<br /><br /><pre class="brush: plain">$(#Dm# % 4)<2?([c]#19ffffff[/c]):([c]#ff1084cb[/c])$</pre><br />Or for the 5th rectangle on the hours ring (n=4), enter<br /><br /><pre class="brush: plain">$(#DH# % 32)<16?([c]#19ffffff[/c]):([c]#ff1084cb[/c])$</pre><br />And so on.<br /><br />I also made an annotated version of the design <a href="https://www.dropbox.com/s/8kshu9xeeavxl2k/binary-num-clock.zw?dl=0">[download]</a> to make telling the time slightly easier; albeit at the cost of looking a little cluttered<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-LFDV5Sh_V-w/VK3khgUeghI/AAAAAAAABSU/VkrzATlnTyE/s1600/Screenshot_2015-01-05-20-45-07.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-LFDV5Sh_V-w/VK3khgUeghI/AAAAAAAABSU/VkrzATlnTyE/s1600/Screenshot_2015-01-05-20-45-07.png" height="200" width="198" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">(16+4):(32+8+4+1) = 20:45</td></tr></tbody></table><br />I tried adding a seconds circle, like in the version from the old blog, but the widgets don't seem to refresh often enough for it to work. Which is a shame, 'cause it's fun to <a href="https://dl.dropboxusercontent.com/u/4635169/time/time2/index-alt.htm">watch the seconds move</a>.<br /><br /><br /><b>Proportional Binary Clock </b><a href="https://www.dropbox.com/s/3v3v4tutm1xucbs/alt-binary-clock.zw?dl=0">[download]</a><br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-acv8N3Bufeo/VK3lbXAGeSI/AAAAAAAABSc/H89C1BBHY4s/s1600/Screenshot_2015-01-07-13-52-11.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-acv8N3Bufeo/VK3lbXAGeSI/AAAAAAAABSc/H89C1BBHY4s/s1600/Screenshot_2015-01-07-13-52-11.png" height="196" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">13:52</td></tr></tbody></table><br />This is pretty much the same as the one above, except, each segment is sized in proportion to the power of two it represents - the 8-segment is four times as big as the 2-segment, etc. I did this to give a better perspective on how much time has passed - because, really, one minute shouldn't take up as much of the clock face as 16 minutes. And also, I just think it looks neat.<br /><br />The mechanics work the same as above. The tricky part is getting the rectangle sizes, curves and rotations right, and getting everything lined up just right, without loosing you mind over how that one bit is half a pixel out of place and there's nothing you can do about it so just let it go, okay!<br /><br />Okay...<br /><br /><br />Oatzy.<br /><br /><br />[Making a backwards, binary, metric clock is left as an exercise for the reader.]<br /><br /><br />* Hint: Try re-writing x as a sum of powers of 2.<br />Bonus: Generalise for other bases.Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com4tag:blogger.com,1999:blog-14769935.post-309237404570245212015-01-01T18:54:00.000-08:002015-01-01T18:54:46.834-08:00Lights Out (Game)I was watching this year's <a href="http://www.rigb.org/christmas-lectures">RI Christmas Lectures</a>, and it reminded me of this game I had when I was much younger. It was, I think, a Christmas present from my grandparents, back in '96. It was called <i>Lights Out</i>, and it looked something like <a href="https://www.google.co.uk/search?q=lights+out+game&source=lnms&tbm=isch&sa=X&ei=cd6lVO_0CovB7AaMjIHYDg&ved=0CAgQ_AUoAQ&biw=1366&bih=696">this</a>.<br /><br />Basically, the console had a 5x5 grid of these rubbery, transluscent buttons with little red lights underneath them. When you press a button, that button and the four adjacent buttons switch state - light on to light off, or off to on. So, in the example below (where blue square are lights), if you press the centre square, the centre square turns off and the four adjacent squares turn on.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-rGY44zTlq-U/VKXdYRbb2BI/AAAAAAAABRQ/4NkpPdThht8/s1600/lightsout-ex.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-rGY44zTlq-U/VKXdYRbb2BI/AAAAAAAABRQ/4NkpPdThht8/s1600/lightsout-ex.png" height="181" width="400" /></a></div><br />In the game, you're given a pattern of lights, such as the one above, and the task is to press buttons until all the lights are out.<br /><br />Interestingly, in the pattern above (on the left), you can clear the board by just pressing the squares that are on at the start (in any order). <br /><br />Anyway, I was reminded of that. Meanwhile, I'd been learning some <a href="http://www.pygame.org/">Pygame</a> for this other thing (that I'll talk about in a later blog). Now, Pygame is a <a href="https://www.python.org/">Python</a> library for making games (amongst other things). So you can probably see where this is going...<br /><br />Coding a 'Lights Out' clone was straightforward enough - the code is short and not too complicated. The above images are actually screenshots. You can look at my code/download it <a href="https://www.dropbox.com/s/pcjmz11a0jncuqf/lightsout.py?dl=0">here</a>. You'll need Python and Pygame to run it.<br /><br />This is a very threadbare version of the game. You can load a level as a text file of five lines of ones and zeros, like <a href="https://www.dropbox.com/s/927jmw3yw43nzc0/level.txt?dl=0">here</a>. Or else you can just play from a blank board - make patterns then try to get rid of them again (not necessarily as easy as it sounds). <br /><br />If you want something more fancy, or if you don't want to install Python and all that, there are probably loads of playable clones online.<br /><br />Now, at this point you're probably thinking "Yeah, that's great. But what about the maths of Lights Out?". And you'd be damn right to ask. If you're interested, there's a good summary, as well as links to articles/papers, on the <a href="https://en.wikipedia.org/wiki/Lights_Out_%28game%29">wikipedia page</a>. I don't want to just repeat what's written there. Basically, it all comes down to <a href="https://en.wikipedia.org/wiki/Linear_algebra">linear algebra</a>.<br /><br /><br />Anyway, have fun with that.<br /><br /><br />Oatzy.<br /><br /><br />[Call this a late Christmas present.] Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-86819194059123568712014-10-14T14:24:00.000-07:002014-10-15T12:46:30.164-07:00Dark Matter and Machine LearningIf you're a long time follower of this blog you'll probably have noticed I haven't posted much in the last two/three years. This is because I've been busy with university stuff. This blog is sort of related to that. The following section is an edited version of the background I wrote for my final year project report (hence why it's so formal). Enjoy.<br /><br /><br /><b>Background - Dark Matter and DM-ICE</b><br /><br />According to current theories, <a href="http://en.wikipedia.org/wiki/Dark_matter">dark matter</a> makes up 23% of the mass-energy density of the universe. However, to date, it hasn't been conclusively observed directly. Instead, it's existence is inferred from large-scale gravitational effects, like the <a href="http://en.wikipedia.org/wiki/Galaxy_rotation_curve">rotation curves of galaxies</a>, and gravitational lensing. Based on rotation curves, we find that the outer edges of galaxies move as if there is more matter present than what we can directly observe. Consequently, it has been theorised that there is a halos of dark matter around the outer edges of galaxies, including our Milky Way.<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-7zHA-ciIqaE/VDwxGQZqEaI/AAAAAAAABNg/iFfUEqqBFoE/s1600/wind-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-7zHA-ciIqaE/VDwxGQZqEaI/AAAAAAAABNg/iFfUEqqBFoE/s1600/wind-diagram.png" height="177" width="320" /></a></div><br />As our Solar System orbits around the galactic centre, it moves though this halo, experiencing an effective dark matter wind. This, combined with the tilt of the Earth's orbit around the sun, means we expect to see an annual modulation in the dark matter flux hitting the Earth. This modulation should have it's maximum around June when the Earth is moving into the wind, and it's minimum around December when it's moving away from the wind, regardless of location on Earth.<br /><br />The current best candidate for dark matter are so-called <a href="http://en.wikipedia.org/wiki/Weakly_interacting_massive_particles">Weakly Interacting Massive Particles</a> (WIMPs). In direct detection experiments, we look at interactions between WIMPs and the nuclei in some target, such as Sodium Iodide (NaI) crystal. The WIMPs scatter elastically off nuclei in the target, and the recoil of the nuclei cause <a href="http://en.wikipedia.org/wiki/Scintillator">scintillation</a> photons to be emitted, with summed energies of ~10keV range. Direct detection is hard, however, since WIMP interactions are rare events, where we expect << 1 count/day/kg. So identifying WIMP events, especially at low energies, requires significant background and noise suppression.<br /><br />Various direct detection experiments (DAMA, CoGeNT, CRESST, CDMS, etc.) have already been run, but none yet have conclusively detected dark matter. Of particular interest from these experiments are the results from the DAMA/NaI and <a href="http://en.wikipedia.org/wiki/DAMA/LIBRA">DAMA/LIBRA</a> experiments. The DAMA experiments (Gran Sasso, Italy) ran direct detection using NaI(Tl) (Thallium doped Sodium Iodide) crystals, and observed an annual modulation in the 2-6keV energy region, which they claim is the result of the dark matter wind. However this interpretation is disputed. It is argued that the modulation could have come from some other unaccounted for signal – for example <a href="http://en.wikipedia.org/wiki/Muon">muon</a> flux also has an annual modulation with peak around June (in the Northern Hemisphere).<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-ED1GsFRmOv8/VDwyKlMFEeI/AAAAAAAABNs/GYkJ6FK8dvo/s1600/dama-plot.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-ED1GsFRmOv8/VDwyKlMFEeI/AAAAAAAABNs/GYkJ6FK8dvo/s1600/dama-plot.png" height="140" width="400" /></a></td></tr><tr align="right"><td class="tr-caption">Source - ArXiv:<a href="http://arxiv.org/abs/0804.2741">0804.2741 </a></td></tr></tbody></table><a href="http://arxiv.org/abs/1106.1156">DM-ICE</a> is a joint venture between University of Sheffield (UK), and University of Wisconsin (Madison, USA). The aim, along with other independent direct detection experiments being run around the globe, is to test the DAMA result by looking for this same modulation, and by ruling out other possible sources of modulation. By running at the South Pole, DM-ICE is in the opposite hemisphere to DAMA, so seasonal effects – such as temperature and muon flux – are reversed, while the dark matter modulation should stays the same. <br /><br /><br /><br /><b>Context</b><br /><br />For my final year project I was looking in particular at the performance stability of the detectors (loss of light yield, etc.) and how to correct the data for those effects. I also did some preliminary modulation analysis - looking for evidence of dark matter in the data.<br /><br />This blog post isn't really to do with any of the work I did on my project, though. Rather, this is looking at how to remove noise from the data. For my project, this work had already been done, so I didn't have to worry about it. So why am I looking at noise removal now? <br /><br />When the academic year was over, my project supervisor asked if I wanted to carry on working on the project (funded) for the summer. It made sense, since there was still work to do, and since I already knew the project well. So mostly I was doing more of what I did for the project. But the supervisor also wanted to adapt the DM-ICE project into a simplified version for groups of 3rd year students to do.<br /><br />Specifically, the students would have to figure out the best cuts to remove noise, then do a modulation analysis to decide if there was evidence of dark matter. <br /><br />So I set up a simplified version of the data and wrote up a description of the project for the students. The supervisor then asked if I'd work through the project and write up a model answer so that he'd have something to mark against. Hence, it was my turn to try the noise removal stuff.<br /><br /><br /><br /><b>Pulse Classification</b><br /><br />In a DM-ICE detector, we have an 8.5kg NaI(Tl) crystal between paired <a href="http://en.wikipedia.org/wiki/Photomultiplier">photo-multipliers</a> (PMTs). These two PMTs - DM0 and DM1 - record scintillation events as voltage pulses (in ADC units). When the raw data is processed, we look for pulses from both of the PMTs which occur within 100 ns of each other. These pairs of pulses are assumed to have come from the same scintillation event. <br /><br />At low energies, pulses look something like this<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-6jtBJn_Kekw/VDxHBZ9XlXI/AAAAAAAABOg/tMUIHTsWG_Q/s1600/signal-ex.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-6jtBJn_Kekw/VDxHBZ9XlXI/AAAAAAAABOg/tMUIHTsWG_Q/s1600/signal-ex.png" height="218" width="400" /></a></div><br />where the various spikes represent single photo-electron events (SPE). Unfortunately, at low energies there is also a significant amount of noise events - specifically, electromagnetic interference (EMI) that look something like this<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-w-tRBtj9Amk/VDxHJccub5I/AAAAAAAABOo/6wHOzFckG74/s1600/emi-ex.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-w-tRBtj9Amk/VDxHJccub5I/AAAAAAAABOo/6wHOzFckG74/s1600/emi-ex.png" height="216" width="400" /></a></div><br />and 'thin peaks', that look something like this<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-RuXqIex23S4/VDxHSqrjsgI/AAAAAAAABOw/S0m7VBxYur8/s1600/thinpeak-ex.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-RuXqIex23S4/VDxHSqrjsgI/AAAAAAAABOw/S0m7VBxYur8/s1600/thinpeak-ex.png" height="218" width="400" /></a></div><br />So the task is to identify and remove these EMI and thin peak events.<br /><br />From looking at plots of the pulses, it's fairly straightforward to tell the different event types apart. But computers can 'look' at the pulses. Instead, we have to define some parameter - values that we can calculate that will tell the computer something about the shapes of the pulses.<br /><br />Take, for example, the EMI pulses. From the plot we can see that EMI pulses oscillate rapidly between positive and negative ADC values, where the SPE and thin peak events don't. So we can define some parameter, which we'll call 'emi', which basically counts how many times the pulse goes from positive to negative (or negative to positive) in the first 40 bins of the pulse.<br /><br />So since the emi value is going to be much higher for EMI-type pulses than for SPE or thin peaks, identifying and removing EMI events is relatively easy.<br /><br />Separating the SPEs from the thin peaks is less straightforward. For this, we calculate various other parameters - for example, since thin peaks will typically only have one peak, while an SPE event will have several, we can look at the peak number. Other things we can look at are how quickly the pulse decays to zero (log-meantime), or how the pulse energy is distributed. The details of how these parameters are calculated aren't important to this blog.<br /><br /><br /><br /><b>Human Learning</b><br /><br />What you can do, then, is look at a bunch of pulses - plot it, what type of event is it? What are it's parameter values? You collect together the results and you ask, what sort of emi values do EMI-type events have? What sort of peak numbers do SPEs and thin peaks have. How can you use that information to tell the peaks apart?<br /><br />And that's fine. But it's also boring. You have to plot, and calculate, and record. Repeat. Analyse. Boring.<br /><br />I subscribe to the philosophy of "why do yourself what you can make a computer do?". In practice this usually means making the work more '<a href="http://xkcd.com/1319/">complicated</a>' in the short term, but once it's done it makes life a whole lot easier. And while the work is more complicated, it's at least more interesting as well.<br /><br />So what I did was I wrote a program. It looks something like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-Aq7AO09Lgzg/VDw6b6EY3QI/AAAAAAAABN4/IfVwNrKzOFc/s1600/ui-ex.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-Aq7AO09Lgzg/VDw6b6EY3QI/AAAAAAAABN4/IfVwNrKzOFc/s1600/ui-ex.png" height="312" width="400" /></a></div>(I used Python with <a href="http://www.numpy.org/">Numpy</a>, <a href="http://matplotlib.org/">Matplotlib</a> for plotting, and <a href="http://www.riverbankcomputing.co.uk/software/pyqt/download">PyQt4</a> for the GUI.)<br /><br />Basically it plots the pulses and presents you with three buttons - signal (SPE), noise (thin peak), and EMI. On the front-end, all the user has to do is look at the plot and decide what type of event the pulse is. Meanwhile, on the back-end, the program calculates the parameters, and sorts them according to the user's classifications.<br /><br />Once you've classified a set of pulses, you're presented with histograms of the different parameters, like this (for the emi value parameter)<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-zZ4eJy4Wh2w/VDw8AVVE53I/AAAAAAAABOE/eG_O35ys8Ow/s1600/emi-hist-nocut.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-zZ4eJy4Wh2w/VDw8AVVE53I/AAAAAAAABOE/eG_O35ys8Ow/s1600/emi-hist-nocut.png" height="221" width="400" /></a></div><br />where the different event types are plotted in different colours. This makes life a lot easier. For example, in the above, it's immediately apparent that EMI-type events can be removed from the data by cutting any event with an emi value above 25.<br /><br /><br /><br /><b>Machine Learning - Naive Bayes Classifiers</b><br /><br />Lets make this more interesting. Sure, we can click through a bunch of peaks telling the program what's what. But wouldn't it be cooler if we could teach the program to tell the events apart itself?<br /><br />Yes. Yes it would.<br /><br />This is where machine learning comes in. Now there are various machine learning techniques, but the one I used in the program is called a '<a href="http://en.wikipedia.org/wiki/Naive_Bayes_classifier">Naive Bayes Classifier</a>'. This is the sort of thing that's used in <a href="http://en.wikipedia.org/wiki/Bayesian_spam_filtering">spam filters</a> - it's a way of calculating, for example, what is the probability that a particular email is spam given that it mentions, say, penis enlargement or Nigerian princes.<br /><br /><br /><i><b>Quick Introduction - Bayes' Theorem</b></i><br /><br />Imagine there is a person. Sight unseen, what is the probability they are female? Well, given that there are roughly equal numbers of males and females in the world, the probability is about 50%. This is called the <a href="http://en.wikipedia.org/wiki/Prior_probability">prior probability</a>. <br /><br />[Aside: in this example, we're assuming for simplicity that gender is a binary state.]<br /><br />Now, we're given a new piece of information - this mystery person's height is 5'4 (five feet and four inches). Now the question is, what is the probability the mystery person is female, given they are 5'4? Well, females tend to be shorter than males (on average) so we might say that the mystery person is more likely to be female that male. But how do we quantify this new probability?<br /><br />This is where <a href="http://en.wikipedia.org/wiki/Bayes%27_theorem">Bayes' Theorem</a> comes in. It looks like this<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-2fbE3T0iTbM/VDxMwOgrLFI/AAAAAAAABPI/RqChNPNKF_M/s1600/bayes-theorem.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-2fbE3T0iTbM/VDxMwOgrLFI/AAAAAAAABPI/RqChNPNKF_M/s1600/bayes-theorem.png" /></a></div>Basically, this calculates the probability of a hypothesis (H) given some observation/evidence (O). p(H|O) is called the <a href="http://en.wikipedia.org/wiki/Posterior_probability">posterior probability</a>.<br /><br />For our example, we have the hypothesis "female" (F) and the observation "h=5'4". So the terms for this example are:<br /><ul><li>p(F) - The probability of a person being female. This is our prior. [ 50% ]</li></ul><ul><li>p(h=5'4|F) - The probability of a person being 5'4 given they are female. Or to put it more plainly, the probability of a female being 5'4. [ ~15.1% ]*</li></ul><ul><li>p(h=5'4) - The probability of any person being 5'4. This can be calculated as the (weighted) average of the probabilities of a female being 5'4, and a male being 5'4. In other words, the probability can be expanded as<br /><div style="text-align: center;"><br />p(h=5'4) = p(h=5'4|F) p(F) + p(h=5'4|M) p(M)</div><br />where p(h=5'4|M) is the probability of a male being 5'4 [ ~1.8% ]*</li></ul><br />Putting it all together, we calculate the (posterior) probability of the mystery person being female given their height is 5'4, as<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-BsGJ1oQoVLk/VDxOY49_DpI/AAAAAAAABPU/9FobORJJH9g/s1600/gender-bayes1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-BsGJ1oQoVLk/VDxOY49_DpI/AAAAAAAABPU/9FobORJJH9g/s1600/gender-bayes1.png" /></a></div><br />As we expected, this probability is much greater than 50% - the mystery person is more likely to be female than male, given their height.<br /><br />*Aside: <a href="https://www.census.gov/compendia/statab/2012/tables/12s0209.pdf">These figures</a> come from average heights for Americans aged 20-80 for 2007-2008.<br /><br />Obviously the probabilities would vary depending on where the mystery person is from, how old they are, etc. Since all we know about this mystery person is their height, we shouldn't really make assumptions about their background. But for the sake of demonstration (and convenience), these values are sufficient.<br /><br /><div style="text-align: left;"><br /></div><div style="text-align: left;"><i><b>Gaussian Naive Bayes - When the Training Set is Small</b></i></div><br />So how does this apply to noise removal?<br /><br />Let's go back to EMI - we can ask, what is the probability that an event is EMI-type given it has an emi value of 30?<br /><br />Using Bayes' Theorem, we have<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-54bQEL_eerQ/VDxRPyzXmyI/AAAAAAAABPg/ACNGKtW-nsw/s1600/emi-bayes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-54bQEL_eerQ/VDxRPyzXmyI/AAAAAAAABPg/ACNGKtW-nsw/s1600/emi-bayes.png" /></a></div><br />Now we need to calculate the probabilities. We're going to assume we have a training set of (31) events that have already been classified. From this we can calculate/estimate these probabilities.<br /><br />The prior, p(E), is pretty straightforward - what fraction of the events in the training set are EMI? This turns out to be around 22.6%<br /><br />The probability of an EMI-type event having an emi value of 30 - p(emi=30|E) - is a little trickier to find. If our training set is small we may have no events with an emi value of exactly 30. But there could, at the same time, be several events with emi values slightly above or below 30.<br /><br />Here then we have to make an assumption - we're going to assume that the distribution of emi values (for each event type) can be approximated by a <a href="http://en.wikipedia.org/wiki/Normal_distribution">normal distribution</a>. This approach is called the 'Gaussian Naive Bayes', and is usually a reasonable approximation - if you look at the plot of emi values higher up, you'll see that they are roughly normally distributed.<br /><br />So to find p(emi|E) we need to calculate the mean and standard deviation of the emi values for the EMI-type events in our training set. Obviously, when the training set is relatively small, the mean and standard deviation are going to have a large margin of error. So it's important to make sure we have a sufficiently large training set.<br /><br />For p(emi=30|!E) - that is, the probability that an event that isn't EMI-type (signal or thin peak) has an emi value of 30 - we do the same procedure of calculating the mean and standard deviation of emi values for non-EMI events, again assuming a normal distribution.<br /><br />For my training set of 31 events, the probability works out at p(E|emi=30) = <i> 97.96%</i><br /><br />In other words, an event with an emi value of 30 is almost certainly an EMI-type event.<br /><br /><div style="text-align: left;"><br /></div><div style="text-align: left;"><i><b>Machine Peak Classification</b></i></div><br />For the full Naive Bayes Classifier, you just repeat the process above for all the parameters, then combine the probabilities. For example, the probability that an event is noise given its log-meantime value and peak number, would be given by<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-m_RCT0twKb0/VD0_VHLeQdI/AAAAAAAABQk/i7HBVVhymss/s1600/noise-bayes.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-m_RCT0twKb0/VD0_VHLeQdI/AAAAAAAABQk/i7HBVVhymss/s1600/noise-bayes.png" /></a></div><br />It's 'naive' because it assumes all the parameters are independent of each other. This isn't strictly true, but it's an acceptable approximation.<br /><br />In fact, in practice, rather than calculating the probability, we calculate the '<a href="http://en.wikipedia.org/wiki/Log-likelihood_ratio">log-likelihood ratio</a>'<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-cZEwoS2ruNQ/VD0_kMFIypI/AAAAAAAABQs/1FzyCVQ1AMI/s1600/log-likelihood.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-cZEwoS2ruNQ/VD0_kMFIypI/AAAAAAAABQs/1FzyCVQ1AMI/s1600/log-likelihood.png" /></a></div><br />where Pi are the various parameters. From that it's pretty straightforward to extract the probability that an event is noise given its parameters. Alternatively, we can just say that if the log-likelihood is greater than 0 - if the probability of being noise is greater than the probability of being signal - then the pulse is classified as noise.<br /><br />The point here is that, rather than investigating the differences between the different pulse types ourselves, then hard coding how to tell pulse types apart - e.g. explicitly writing in the code that an event is EMI-type if it has an emi value greater than 25, etc. - we instead show the code some examples of different pulses, and it 'learns' to tell the difference itself. This saves us a lot of work. In fact, we never even have to know what the differences between pulse parameters are.<br /><br />When you're using the program, you can have it display what the algorithm thinks pulses are (and the associated probabilities). So you can click through a bunch of pulses, telling the algorithm what they are, and the algorithm will tell you what it thinks they are. As you 'teach' it, the algorithm will get better at telling the difference between pulses. And when you're happy with how well its classifications match up with your own, you can press a button and it will classify the rest of the pulses for you. How cool is that?<br /><br /><br /><br /><b>Finding the Best Cuts</b><br /><br />Once the algorithms is adequately trained, we could just set it to work, going through the data, classifying and removing noise events. But for their project, the students aren't looking at machine learning, or statistics, or anything like that. Instead, they're asked to look for cutoff conditions for each of the parameters - for example, a pulse is noise if it has emi > 25.<br /><br />To get a better idea of the problem we can look at our histograms of the parameters, with the different pulse type plotted in different colours. This is where the pulse classification comes in useful - the more pulses we classify, the clearer the distributions of parameters.<br /><br />When you're looking at the emi values, as in the plot higher up, the cutoff is pretty easy - there's a clear gap between emi values for EMI-type pulses, and those for signal and thin peaks. For other parameters, the distinction is less clear, and often we have to make some trade off between leaving behind noise events and removing signal events. For example<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-kNu7cmgMUt0/VDxSn_014yI/AAAAAAAABPs/vfKVQhfWDUk/s1600/logmt-hist-nocut.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-kNu7cmgMUt0/VDxSn_014yI/AAAAAAAABPs/vfKVQhfWDUk/s1600/logmt-hist-nocut.png" height="222" width="400" /></a></div><br />Here, if you want to keep all the signal, you leave behind 23 (thin peak) noise events (34%). If you want to remove all the noise you end up also removing 6 signal events (54%).<br /><br />Aside: Since EMI-type events are easily distinguished and removed with the emi value cut, they are not taken into account when we look at cuts for the other parameters, which are meant to separate signal from thin peaks. <br /><br />So the question is, how can we determine the 'best' cuts?<br /><br />This is basically an <a href="http://en.wikipedia.org/wiki/Optimization_problem">optimisation problem</a> - we want to find the cut for which the maximum amount of noise AND the minimum amount of signal is removed. To do this, we need to come up with some metric/way of scoring how effective a given cut is, then we look for the cut that scores best. <br /><br />We'll define Ni as the number of noise events and Si the number of signal events removed by some cut Ci. Because there are generally more noise events than signal, we're going to score based on the fractions of noise and signal removed - (Ni/N) and (Si/S) - where N and S are the total number of noise and signal events respectively. So the optimisation problem is finding the cut that gives (Ni/N) closest to one and (Si/S) closest to zero, simultaneously. <br /><br />One approach to this optimisation is a '<a href="http://en.wikipedia.org/wiki/Nearest_neighbor_search">nearest neighbour search</a>'. To make this clearer, we can look at a scatter plot with points representing the fractions of signal and noise removed for different cuts. In this set-up, optimising means looking for the point that is nearest the bottom-right corner of the graph (1,0).<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-yqJwoOURryw/VDxwacKxmOI/AAAAAAAABQU/LdOz85pD2p4/s1600/nearest-neigh.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-yqJwoOURryw/VDxwacKxmOI/AAAAAAAABQU/LdOz85pD2p4/s1600/nearest-neigh.png" height="233" width="400" /></a></div><br />We can find the distance of each point from (1,0) using Pythagoras, making the scoring function <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-WYwgN4Tgtsw/VDxXWIgdwyI/AAAAAAAABP8/r_kFuCNRHTw/s1600/score.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-WYwgN4Tgtsw/VDxXWIgdwyI/AAAAAAAABP8/r_kFuCNRHTw/s1600/score.png" /></a></div>which we want to minimise.<br /><br />There is one caveat though - a cut that, for example, removes 10% of the signal and 60% of the noise will have the same distance score (0.41) as a cut that removes 40% of the signal and 90% of the noise. The latter removes more noise (almost all of it) but also removes more signal. In this case we have a choice - do we want to remove more noise, or save more signal? <br /><br />To differentiate the two cases we can use the angle between the point and the noise-axis <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-kkHoUhc83m0/VD2KAsMfkkI/AAAAAAAABQ8/Jhy3ts9vXXA/s1600/angle-score.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-kkHoUhc83m0/VD2KAsMfkkI/AAAAAAAABQ8/Jhy3ts9vXXA/s1600/angle-score.png" /></a></div>So if, for example, we want to save as much signal as possible then we want to minimise the angle. Or perhaps for a better balance between removing signal and noise, we should prefer the cut that gives an angle closest to 45deg. For myself, I prefer to save as much signal as possible. In practice, however, having two or more cuts with the same distance score is rare.<br /><br />When it comes to reporting the (distance) scores, they are re-formatted as<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-r4HaJfkUxf8/VDxXhHjEJmI/AAAAAAAABQE/CYhQ3okaEpE/s1600/score-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-r4HaJfkUxf8/VDxXhHjEJmI/AAAAAAAABQE/CYhQ3okaEpE/s1600/score-alt.png" /></a></div>This gives a score of 1 for a cut that removes all noise and no signal, a score of -1 for a cut that removes all signal and no noise, a score of 0 for a cut that removes no signal or noise at all, etc. This score could be used in the optimisation algorithm (where it would need to be maximised), but the results would be the same, and the Pythagorean distance is easier to calculate.<br /><br />Below is an example cut from this algorithm.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-omNkEHpZ2Cg/VDxCYPDINbI/AAAAAAAABOU/nYa6piOfZfQ/s1600/logmt-hist-cut.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-omNkEHpZ2Cg/VDxCYPDINbI/AAAAAAAABOU/nYa6piOfZfQ/s1600/logmt-hist-cut.png" height="267" width="400" /></a></div>Notice that it sacrifices one signal event for 15 noise events, but prefers to keep one more signal event, rather than removing an extra 5 noise events.<br /><br />Ultimately, this technique of removing noise with parameter cutoffs is less effective than the Naive Bayes, largely because it considers all the parameters individually/independently. However, there are things called '<a href="http://en.wikipedia.org/wiki/Support_vector_machine">support vector machines</a>' (SVM) which would probably be ideal for this problem. They look for a dividing 'line' between signal and noise, as above, but they consider all of the parameters together. <a href="http://opencv.org/">OpenCV</a> has an implementation of SVM, so I might give that a try at some point.<br /><br /><br /><br /><b>Last Word</b><br /><br />As it started out, I just wanted to throw together a quick GUI to make my life easier, without worrying about error handling or bugs or any of that other fiddly stuff. Then I decided I wanted to try out some machine learning. And the more I worked on the program, the more features I thought of, and added.<br /><br />So now, I have a program designed specifically for removing noise from DM-ICE data. But since DM-ICE has already had its noise removed (as far as possible), the program is basically of no use to anyone. Well, except maybe to any students working on this project. But that would be cheating - hence why I'm wary of posting the code.<br /><br />Still, it was a fun little project.<br /><br /><br />Oatzy.<br /><br /><br />[Keep an eye out for news on DM-ICE.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-1034944383332426522014-09-18T14:36:00.000-07:002014-09-18T14:36:09.704-07:00#IceBucketChallenge - A Viral CampaignI know this blog is a little late to the game. In fact, I started writing it while it was still relevant... but then I got distracted. Such is life.<br /><br /><br /><b>Background</b><br /><br />You probably already know (or vaguely remember) what the <a href="http://knowyourmeme.com/memes/ice-bucket-challenge">Ice Bucket Challenge</a> is. Basically if you're nominated you have to dump a bucket of ice water over your head, or else you have to donate money to some charity - most commonly <a href="http://alsa.org/">ALSA</a>, the <a href="http://en.wikipedia.org/wiki/Amyotrophic_lateral_sclerosis">Amyotrophic Lateral Sclerosis</a> Association.<br /><br />You then nominate 3 more people, who have 24 hours to do the same thing. In some variants, you also make a small donation even if you dump the ice water over your head, or else you make a bigger donation if you don't (some specify $100).<br /><br />Anyway, what I was interested in is how the challenge spread - this was a 'viral' campaign in a very true sense. So why not try to model how the campaign spread as we would model the spread of a virus?<br /><br /><br /><b>The Viral Model</b><br /><br />I've written about this sort of <a href="http://oatzy.blogspot.co.uk/2010/09/social-viruses.html">thing</a> <a href="http://oatzy.blogspot.co.uk/2011/01/compartmental-model-of-follower-growth.html">several</a> <a href="http://oatzy.blogspot.co.uk/2012/05/sexuality-and-population-dynamics.html">times</a> <a href="http://oatzy.blogspot.co.uk/2010/09/modelling-zombie-apocalypse.html">before</a>. The basic idea is this - the population is divided into three groups:<br /><br />Susceptible (S) - The population that hasn't been exposed to a disease/virus, but is susceptible to infection<br />Infectious (I) - The people who have been exposed, and can infect other people<br />Recovered (R) - The people who have been infected, but have recovered. It's generally assumed that these people can't be re-infected. (But there are variations).<br /><br />For the ice bucket challenge, we can look at a direct analogy as<br /><br />S - Those who haven't been nominated<br />I - Those who have been nominated and are taking the challenge (so can nominate other people)<br />R - Those who have completed or those who declined the challenge (and can't nominate anyone else)<br /><br />We can <a href="https://www.lucidchart.com/">draw</a> the model as a flowchart, showing how people move between the different groups,<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-nHZAxJeVb3U/VBoFt27vaZI/AAAAAAAABME/qUr9ot8KuK8/s1600/sir-flow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-nHZAxJeVb3U/VBoFt27vaZI/AAAAAAAABME/qUr9ot8KuK8/s1600/sir-flow.png" height="164" width="400" /></a></div>Here, we've divided nominees into those who accept the challenge (S->I) and those who don't accept (S->R). So, now we can describe the model as a system of differential equations,<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-b52Ps01VHDY/VBoF3M8E8cI/AAAAAAAABMM/trsDNdSfDEs/s1600/sir-diff.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-b52Ps01VHDY/VBoF3M8E8cI/AAAAAAAABMM/trsDNdSfDEs/s1600/sir-diff.png" /></a></div><br />Where the factors (α, β, γ) describe what proportions of each group move to where. <br /><br />The most interesting part is the 'S*I' terms in the first two equations - this basically says that the number of newly infected people is proportional to the number of susceptible people AND the number of infectious people.<br /><br />This makes the system self-limiting, meaning that the number of infected people can't just grow to infinity - since that's not what we observe. Instead, what we see is an increase to some maximum, followed by a steady drop-off. For example,<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-Xbp3Epjmakg/VBoF9kzdBmI/AAAAAAAABMU/t87H2HyLxtA/s1600/yt-trend.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-Xbp3Epjmakg/VBoF9kzdBmI/AAAAAAAABMU/t87H2HyLxtA/s1600/yt-trend.png" /></a></div>We don't have hard data on how many people did the challenge over time, but we can get an idea of what happened by looking at the YouTube search numbers for the phrase 'ice bucket challenge' (above, via <a href="http://www.google.com/trends/explore#q=ice%20bucket%20challenge&date=today%203-m&gprop=youtube&cmpt=q">Google Trends</a>). <br /><br />I mean, it seems reasonable to assume some loose correlation between the number of people doing the challenge, the number of videos of people doing the challenge, and the number of searches for those videos. Searches peaked on August 21st, in case you were wondering.<br /><br /><br /><b>A More Discrete Model</b><br /><br />The downside to this 'S*I' term is that it makes the equations non-linear, meaning they can't be solved analytically - that is, you can't come up with an 'exact' equation for the number of people doing the challenge on a given day, for example.<br /><br />But we can do a numerical simulation, instead.<br /><br />In fact, this is a more reasonable way of looking at the system, since we're interested in a discrete time step of one day - i.e. the 24 hours nominees have to complete the challenge. For the simulation, we're also going to rounded the numbers of people in each group (after each time step) to whole numbers, since you can't (or shouldn't) divide a person into fractions. <br /><br />Now, we need to define some parameters. First of all we need to define our start populations. We'll call the initial susceptible population S0 - this could be, for example, the <a href="http://www.worldometers.info/world-population/">Earth's population</a> (which was around 7.16bn when I ran my simulations). We'll assume that one person is infected as a starting point - the originator of the challenge (I0 = 1). And we'll assume that no-one else has done the challenge at the start, so R0 = 0.<br /><br />For the constants (α,β,γ), the easiest one to define is γ - the 'recovery' rate. We assume that after the 24 hour challenge period nominees are no longer 'infectious', therefore γ = 1.<br /><br />For α and β, we have the <i>total</i> rate of infection/nomination defined as (α+β). We're given that each challenge completer gets to nominate three new people, so we can define (α+β) such that in the first step (when there's only one infectious person) we have (α+β)*S0*I0 = (α+β)*S0 = 3. Therefore (α+β) = 3/S0.<br /><br />Now, α and β are related to the proportions of nominees that accept and decline the challenge, respectively. So we can redefine the constants as α = a/S0 and β = b/S0, such that (a+b) = 3, or alternatively α = a/S0 and β = (3-a)/S0. So now we can look at 'a' as the average number of nominees who accept the challenge.<br /><br />So to tie it all together, we have the system of (difference) equations,<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/--iohXkbnjYc/VBoGFcbkppI/AAAAAAAABMc/Pi1leVCVRB4/s1600/sir-dis.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/--iohXkbnjYc/VBoGFcbkppI/AAAAAAAABMc/Pi1leVCVRB4/s1600/sir-dis.png" /></a></div>And it's pretty straightforward to write some code that'll run through those equations.<br /><br /><span style="font-size: small;"><br /></span><span style="font-size: small;"><b>So we've got our model, what now?</b></span><br /><br />Having a model is all well and good, but why bother? Well, now we can start asking questions. For example, how fast does the campaign spread? How long will it take for the challenge to die out, and how many people will have taken part by that point? And what happens when we change the number of people who accept the challenge?<br /><br />First of all, if we run the simulation (with a = 2.5) and plot I(n) - the number of people doing the challenge on any given day - we get something like this<br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-C0xgatlNjQI/VBoJmXLGWFI/AAAAAAAABNA/NJpIgvcULtQ/s1600/sir-i-2.5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-C0xgatlNjQI/VBoJmXLGWFI/AAAAAAAABNA/NJpIgvcULtQ/s1600/sir-i-2.5.png" height="241" width="400" /></a></div>[where I(n) has been normalised so that the maximum is 100, as Google Trends does]. <br /><br />This is about what we expected - a rise and a fall. Though it's worth noting the shape is a little different from the YouTube searches above; the simulation drops off quickly, while the search numbers have a longer tail. This could be because, even after the challenges are done, there's a latent interest in (re)watching the videos. Or it could just be that this model is not entirely accurate...<br /><br /><br />So what happens when we vary 'a'?<br /><br />We can start by assuming everyone accepts the challenge (a = 3). In this case, eventually everyone in the world does the challenge - specifically within 21 days of the first challenge. But that isn't very realistic.<br /><br />So let's say that on average 1.5 or 2 of those challenged accept. In these cases, the ice bucket phenomenon ends before everyone can be challenged. For a = 2, the challenge ends after 50 day, with ~13% of the population going unchallenged. On the other hand, for a = 1.5 the challenge takes significantly longer to end (over a year).<br /><br />In fact it turns out that for any a < 1.6030165.. the challenge will take a significant amount of time to end - the number of 'infectious' people will eventually reach 1, and stay there until S <= S0/(2a) (remember we're rounding each group to the nearest whole number). For a <= 0.5 the challenge ends almost immediately.<br /><br />Now, we know that for a = 3, the population S goes to zero (everyone is challenged), whereas for a = 2 the challenge stops before S can go to zero. So we have the question - what is the smallest value of 'a' for which S goes to zero? If you do a bit of interpolating, you can figure out that this critical value comes out at around 2.7405349.. At this value of 'a', the challenge ends after 23 days. In other words, if on average 2.74.. (~91%) of the people nominated accept the challenge, then after 23 days everyone in the world will have been challenged.<br /><br />From what I've seen myself, it seems like nearer 1 in 3 people accept the challenge on average. So, as viral as the campaign was, it was never going to take over the world.<br /><br />If you play around a bit, you'll find that the critical values of 'a' are dependent on the initial population (S0). But, as far as I can tell, it's not possible to derive these critical values analytically. (Answers in the comments if you can prove otherwise).<br /><br /><br /><b>Why wait to be nominated..?</b><br /><br />At this point, you're probably thinking this isn't a very realistic model. And you'd be right. So lets make it a bit more complicated.<br /><br />In particular, lets add spontaneous participation - that is, people who aren't directly nominated, but who see all the other people doing the challenge, and decide they want to take part too.<br /><br />We'll assume that this participation is proportional to those who have already done the challenge (I and R). So to start with, we need to separate the 'recovered' group into those who actually did the challenge (R), and those who declined (D).<br /><br />The new model looks something like this,<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-V_HbFK5TuHQ/VBoHB6kbM3I/AAAAAAAABMk/3WVan4CmpCU/s1600/sird-flow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-V_HbFK5TuHQ/VBoHB6kbM3I/AAAAAAAABMk/3WVan4CmpCU/s1600/sird-flow.png" height="221" width="400" /></a></div><br /><br />With difference equations,<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-RdwVQiofcco/VBoJDJakbpI/AAAAAAAABM4/vC-tj_ny5_E/s1600/sird-dis.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-RdwVQiofcco/VBoJDJakbpI/AAAAAAAABM4/vC-tj_ny5_E/s1600/sird-dis.png" /></a></div>In the flowchart, we've introduced this new factor 'δ', the 'inspiration' rate. If we re-define it, like we did for the infection rate, as d/S0, then 'd' can be loosely interpreted as the average number of people inspired to take part by each person who's already done the challenge.<br /><br />Now we can look at how this 'd' factor affects the things we looked at before - how long it takes for the challenge to end, etc.<br /><br />Let's start by assuming that the average number of nominees accepting the challenge, 'a', is 1 (out of 3). What value of 'd' do we need for S to go to zero? Do a little interpolating again and you get d = 0.8668653 - that is, if each person who pours a bucket of water over their head inspires (on average) 0.867.. people to do the same, then everyone in the world will participate, within 26 days of the challenge starting. For a = 2, we need d = 0.4046021 for S goes to 0. And so on...<br /><br />What's a plausible inspiration rate? For a normal person, probably zero, while for a celebrity... I don't know. But on average 'd' is probably very close to zero. I mean, we know for a fact that significantly less than the entire population of Earth has been nominated/taken part in the challenge.<br /><br />If you plot I(n) for a = 1 and d = 0.1 you get something like this,<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-kT_o0RdpYJ8/VBsQtIAfZ5I/AAAAAAAABNQ/axZtVoBGX5w/s1600/sird-i-1-0.1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-kT_o0RdpYJ8/VBsQtIAfZ5I/AAAAAAAABNQ/axZtVoBGX5w/s1600/sird-i-1-0.1.png" height="237" width="400" /></a></div><br />In this case, you have that same rise and fall - but this time, you have a longer tail, like we see in the YouTube searches. Is this proof that this iteration of the model is more accurate? Maybe. But as I pointed out before, the YouTube searches don't necessarily accurately represent the number of people taking the challenge over time.<br /><br /><br /><b>Social Pressure</b><br /><br />When a friend does a thing for charity, then publicly calls you out to take part, there's a certain amount of social pressure to comply. I mean, if a friend dumped water over their head (just because), then asked you to do the same thing, probably you'd look at them like they were a crazy person. Anyway, that kind of social pressure is implicitly included in the 'infection' rate - more social pressure, bigger 'a'.<br /><br />Instead, the sort of social pressure I'm talking about here is the kind that goes: "I should accept the challenge because so many other people have already done it". Or alternatively, "it's okay for me to accept the challenge, since so many other people have already done it". <br /><br />In other words, the more people accept and complete the challenge, the more likely a nominated person is to accept too. Mathematically, we can introduce this with a term in 'S*I*R'. Or alternatively, we can keep the term as 'a*S*I', but make the factor 'a' a function of R -> a(R).<br /><br />Anyway, if you're interested, you can try investigating that yourself. Or try adapting the model in some other ways. But beyond a point you can end up complicating a model more than improving it.<br /><br /><br /><b>A Network Theory Approach to Nominating</b><br /><br />So I was eventually nominated for the challenge. But I'm a wimp, so I declined to dump ice water over my head, instead making a donation to the <a href="http://www.mndassociation.org/">Motor Neuron Disease Association</a> (the UK equivalent of ALSA).<br /><br />For my nominations, I wanted to try and maximise spread. So I nominated the 3 of the people in my Twitter network who are the most active and well connected, and who I thought would be up for accepting the challenge. Plus, as a secondary effect, I figured they might nominate other people in my Twitter network - the network theory equivalent of wishing for more wishes. <br /><br />In the end, one ignored the nomination, one acknowledged but didn't accept, and one accepted (in the form of a donation) but didn't nominate anyone else. So I guess that theory didn't quite pan out. But I did at least encourage more charitable giving.<br /><br /><br /><b>So Yeah</b><br /><br />If we had real world data we could maybe test the accuracy of these models. But even without, we can get a sense of how the challenge behaves - for example, we find that there's a critical 'challenge acceptance ratio' that determines whether the viral campaign will go 'pandemic'.<br /><br />In theory, you could apply this sort of model to any viral campaign, or just anything that spreads 'virally'. The nice thing about the Ice Bucket Challenge in particular, though, is that it has well defined rules for how the challenge spreads from one person to the next. <br /><br />So, yeah..<br /><br /><br />Oatzy.<br /><br /><br />[I need an editor, my pronouns are all over the place.]<!--1-->Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-52940024175052988652014-08-11T15:37:00.000-07:002014-08-11T15:37:38.754-07:00How Long Will a Prime Number Be?File this under things I think about when I'm trying to get to sleep.<br /><br /><br /><b>The Question</b><br /><br />The idea is this - pick a random integer between 0 and 9. If it's prime, stop. If it's not prime, pick another random integer and stick it on the end of the first number. If this new number is prime, stop. If it isn't prime, pick another random integer, and so on.<br /><br />So, for example:<br /><br />1 -> not prime<br />2 -> 12 -> not prime<br />7 -> 127 -> prime -> stop.<br /><br />The question is this - on average, how long would we expect the number to be when we get a prime?<br /><br /><br /><b>Sometime Programming Just Won't Cut It</b><br /><br />Normally, this is where I'd bust out Python and write a little program to run through the procedure a few times and see what happens. The problem is, <a href="http://en.wikipedia.org/wiki/Primality_test">testing whether a number is prime</a> tends to be really slooooow. <br /><br />The 'easiest' algorithm, <a href="http://en.wikipedia.org/wiki/Trial_division">trial division</a> - literally checking if the number is divisible by any of the integers less than it - has (worst case) speed O(sqrt(n)). What this means is, each time we add a digit to our number, it will take ~sqrt(10) = 3.16 times longer to check if it's prime. And that time soon adds up. There are quicker algorithms, for example <a href="http://en.wikipedia.org/wiki/AKS_primality_test">AKS</a>, but they're too complicated to bother with. <br /><br />Instead, we can figure out the answer with some good old-fashioned maths.<br /><br /><br /><b>Some Good Old-Fashioned Maths</b><br /><br />Lets re-state the problem - what we want to know is the expected length of the prime number. Here we use the standard definition for the <a href="http://en.wikipedia.org/wiki/Expected_value">expectation value</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-FzkWyyt9t8Q/U-k2Sd22zyI/AAAAAAAABKQ/Sl5yYy1KsTQ/s1600/exN.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-FzkWyyt9t8Q/U-k2Sd22zyI/AAAAAAAABKQ/Sl5yYy1KsTQ/s1600/exN.png" /></a></div><br />So now the problem is find p(N), which in this case is the probability that a number of length N is prime. Now, as any romantic mathematician will tell you, prime numbers are mysterious things, which are scattered about the number line seemingly at random. However, what we do have is the '<a href="http://en.wikipedia.org/wiki/Prime_number_theorem">Prime Number Theorem</a>', which estimates the number of prime numbers smaller than n, as<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-xQ7UqL5KmXo/U-lAEZzkopI/AAAAAAAABK8/jcFQ7pQjC1A/s1600/piN1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-xQ7UqL5KmXo/U-lAEZzkopI/AAAAAAAABK8/jcFQ7pQjC1A/s1600/piN1.png" /></a></div>where ln(n) is the <a href="http://en.wikipedia.org/wiki/Natural_logarithm">natural logarithm</a>. Here's a <a href="https://www.youtube.com/watch?v=l8ezziaEeNE">Numberphile video</a> explaining the theorem.<br /><br />So, for one digit numbers (numbers less than 10) there are ~10/log(10) = 4.2 prime numbers (well, technically 4, but this is an estimate). So the probability that a one digit number is prime is estimated as 0.42. The estimate gets better as the number of digits gets bigger.<br /><br />Now, for numbers of two or more digits, the number of primes that are N digits long is (approximately) the number of primes less than 10^N minus the number of primes less than 10^(N-1). So then, the probability of an N-digit number being prime is the number of N-digit primes divided by the total number of N-digit numbers. It can be shown (homework) that this probability is given by<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-_7R6NyTO7z8/U-k2oxY8ojI/AAAAAAAABKU/SyQ2T5utEtk/s1600/pN-eqn.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-_7R6NyTO7z8/U-k2oxY8ojI/AAAAAAAABKU/SyQ2T5utEtk/s1600/pN-eqn.png" /></a></div><br />Plotting this function, we see that the probability goes to zero as N goes to infinity<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-2WyYk42ZfS8/U-k3NSk4fZI/AAAAAAAABKk/gTDip1vzbeE/s1600/pN.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-2WyYk42ZfS8/U-k3NSk4fZI/AAAAAAAABKk/gTDip1vzbeE/s1600/pN.png" height="175" width="320" /></a></div><br />In other words, the longer the number gets the less likely it is to be prime. In this case, you can probably guess what the expected length of our prime will be.<br /><br />So we have<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-Ng83-jonPLo/U-k2yeygUSI/AAAAAAAABKc/0Oor-4P4LOU/s1600/exN1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-Ng83-jonPLo/U-k2yeygUSI/AAAAAAAABKc/0Oor-4P4LOU/s1600/exN1.png" /></a></div><br />And as N->infinity, N p(N) -> 1/ln(10). What this means is the sum doesn't converge - it goes to infinity. So on average we expect our prime to be infinitely long.<br /><br /><br /><b>So Basically</b><br /><br />To cut a long story short, the procedure is most likely to stop when the number is only one digit long. Or else, the longer the number gets, the less likely the procedure is to stop. This is another reason why the programming solution was a non-starter. <br /><br /><br />Oatzy.<br /><br /><br />[Now stop doing maths and go to sleep.] Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-4784413521921075112013-12-22T12:16:00.001-08:002013-12-22T13:36:41.267-08:00Modelling the Survival of Chocolates<br /><span style="font-family: Arial,Helvetica,sans-serif;">So it's been about a year since I last blogged. I've been kinda busy with university stuff, and growing my facial hair into something approaching a beard. I might write a blog about it at some point.</span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">Anyway, I've got a little free time now - at least until after Christmas - so here we are. Let's waste some time...<br /><br /><br /><b>"This is a real paper, published in a proper journal"</b><br /><br />So the other day, my dad hands me this print out of an academic paper, "this seems like your sort of thing". The paper is called<br /><i><br />"The survival time of chocolates on hospital wards: covert observational study"</i> <span style="font-size: x-small;">[1]</span><br /><br />The basic set-up is this: staff at various hospitals put out boxes of Roses and Quality Street chocolates on wards, and (covertly) recorded every time one was taken. From this, they analysed the 'survival' of the chocolates, according to <a href="http://en.wikipedia.org/wiki/Kaplan-Meier_estimator">Kaplan-Meier</a> survival analysis. </span><br /><div class="separator" style="clear: both; text-align: center;"><span style="font-family: Arial,Helvetica,sans-serif;"><a href="http://4.bp.blogspot.com/-JVuhOJaGiaY/UrcR3Ak-b5I/AAAAAAAABH8/AhfXvDIKmtU/s1600/paper-choc-plot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-JVuhOJaGiaY/UrcR3Ak-b5I/AAAAAAAABH8/AhfXvDIKmtU/s1600/paper-choc-plot.png" /></a></span></div><span style="font-family: Arial,Helvetica,sans-serif;">They found that the rate of chocolate consumption fit an exponential decay, with the median survival time for a chocolate being 51 minutes, and the survival half life ("time taken for 50% of the chocolates to be eaten") being 99 minutes. They also found that Roses chocolates were preferred over Quality Street.<br /><br /><br /><b>"Damn it, man, I'm a physicist, not a doctor!"</b><br /> </span><br /><span style="font-family: Arial,Helvetica,sans-serif;">So I'm reading this paper, and I'm thinking "I reckon I could model this". So I had a crack at it. </span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">Basically, what we've got here is an <a href="http://en.wikipedia.org/wiki/Agent_based_model">agent-based modelling</a> problem. Individuals encounter a box of chocolates at random intervals. Each box has a variety of different types of chocolates. So each individual will look through the box for any chocolate(s) they like the look of, and help themselves.<br /><br />We can make a few simplifications to this. First of all, we work in fixed, discrete time intervals. In each time interval, some number of people will encounter the box of chocolates - one person per interval in the simplest model, and some random number of people in a more complex model.<br /><br />Now, different people have different preferences. We could generate for each person a random set of preferences, but for simplicity we'll assume some 'average preference probability'. In other words, imagine there are equal numbers of each type of chocolates - what is the probability that any given person will pick type 1, type 2, etc. ? </span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">So, in the model, each person will pick a random chocolate based on this preference probability distribution. This accounts for the fact that some types of chocolate (strawberry) are naturally more popular than others (toffee penny).<br /><br /><br /><b>Here's Where it Gets Maths-y</b><br /><br />We have a box of N chocolates total. There are m different types of chocolate, with each type i=1,..,m having a count of ni such that </span><br /><div style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-9fiWOA5YSKs/UrcaCcDUcOI/AAAAAAAABIM/LQj7Uych-pQ/s1600/eqn1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-9fiWOA5YSKs/UrcaCcDUcOI/AAAAAAAABIM/LQj7Uych-pQ/s1600/eqn1.png" /></a></div></div><span style="font-family: Arial,Helvetica,sans-serif;">Each type also has a 'preference probability' pi with 0 <= pi <= 1 and </span><br /><div style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-pq9Ds7VKaO0/UrcaKhfN6OI/AAAAAAAABIU/YDV_2VMJAFA/s1600/eqn2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-pq9Ds7VKaO0/UrcaKhfN6OI/AAAAAAAABIU/YDV_2VMJAFA/s1600/eqn2.png" /></a></div></div><span style="font-family: Arial,Helvetica,sans-serif;">In each time step dt an individual randomly chooses a type of chocolate, i in {1,...,m}, based on the preference distribution, and one chocolate of that type is removed from the box according to </span><br /><div style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-ouTdpaQ5yxk/UrcaTctggMI/AAAAAAAABIc/sz_YJBIGN18/s1600/eqn3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-ouTdpaQ5yxk/UrcaTctggMI/AAAAAAAABIc/sz_YJBIGN18/s1600/eqn3.png" /></a></div></div><span style="font-family: Arial,Helvetica,sans-serif;">This process is repeated until N(t) = 0<br /><br />In the more complex model, some random number of individuals (following a Poisson distribution) each choose chocolates in each given time step. This is also functionally equivalent to a single individual choosing multiple chocolates in a single given time step.<br /><br /><br /><b>What Differential Does It Make?</b><br /><br />In the simplest model, one person chooses a single chocolate in each time step. This is nice, because it means we can express the model as a set of differential equations, which can be solved exactly. <br /><br />For each type of chocolate we have the rate of consumption given by</span><br /><div style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-5oK44H6IyHQ/UrcaqV3NhgI/AAAAAAAABIk/qqvPde8wrv4/s1600/eqn4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-5oK44H6IyHQ/UrcaqV3NhgI/AAAAAAAABIk/qqvPde8wrv4/s1600/eqn4.png" /></a></div></div><span style="font-family: Arial,Helvetica,sans-serif;">which has standard (exponential decay) solution</span><span style="font-family: Arial,Helvetica,sans-serif;"> </span><br /><div style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-wubkt4Iu58c/UrcaxOPkM0I/AAAAAAAABIs/UruW4Ijz7Cc/s1600/eqn5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-wubkt4Iu58c/UrcaxOPkM0I/AAAAAAAABIs/UruW4Ijz7Cc/s1600/eqn5.png" /></a></div></div><span style="font-family: Arial,Helvetica,sans-serif;">And since the total count N is just the sum of the individual counts ni we get the full solution</span><br /><div style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-__XKhG4_j5U/Urcbl-PpoVI/AAAAAAAABJE/-UxxR38IGjc/s1600/eqn6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-__XKhG4_j5U/Urcbl-PpoVI/AAAAAAAABJE/-UxxR38IGjc/s1600/eqn6.png" /></a></div></div><span style="font-family: Arial,Helvetica,sans-serif;">In other words, it's just the sum of several exponential decays (with different decay rates) - making it, roughly, an exponential decay itself. </span><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-nmzOZfYSryk/UrcO_gvlFOI/AAAAAAAABHo/DJ3mDfx9Frg/s1600/chocolate-diff.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-nmzOZfYSryk/UrcO_gvlFOI/AAAAAAAABHo/DJ3mDfx9Frg/s1600/chocolate-diff.png" /></a></div><span style="font-family: Arial,Helvetica,sans-serif;">In particular, in the special case where all the chocolates are equally preferred such that pi = p for all i=1,..,m we get the solution</span><br /><div style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-aZFWDDNBl4g/Urcblqd3ewI/AAAAAAAABJA/eYIJnOQg4dA/s1600/eqn7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-aZFWDDNBl4g/Urcblqd3ewI/AAAAAAAABJA/eYIJnOQg4dA/s1600/eqn7.png" /></a></div></div><span style="font-family: Arial,Helvetica,sans-serif;">And all that is loosely what was observed on the hospital wards. </span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">But these 'exact' solutions don't capture some of the subtleties of the real life set-up. For example, the toffee pennies might be completely ignored until they are the only option left; whereas, the exact solution assumes that (fractions of) all of the types of chocolate are being taken all the time, just at different rates.</span><br /><span style="font-family: Arial,Helvetica,sans-serif;"></span><br /><span style="font-family: Arial,Helvetica,sans-serif;"></span><br /><b><span style="font-family: Arial,Helvetica,sans-serif;">"We did not seek ethical approval for this study..."</span></b><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">As an improvement on the analytical solution, we can create a computer simulated model, that captures more of the randomness of real life (code below). For the smplest model, the results look something like this</span><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-S2HSuU8Y5VU/UrcOvVYTvuI/AAAAAAAABHg/Gcl0xcX8sP4/s1600/chocolate-sim.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-S2HSuU8Y5VU/UrcOvVYTvuI/AAAAAAAABHg/Gcl0xcX8sP4/s1600/chocolate-sim.png" /></a></div><span style="font-family: Arial,Helvetica,sans-serif;">Notice that in this version of the model, the number of chocolates decays more slowly, and has some of the granularity seen in the observed data plot. </span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">Plus, the randomness means the specific behaviour varies from run to run - for example, the orange line decays relatively quickly, while the blue has a long stretch when nothing is being taken.</span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">Interestingly, this model seems to have a linear decrease for the first ~20-30 time steps in every sample run. This is probably because, for those first few steps, everyone can get what they want. But eventually, certain types run out, and the rate of consumption decays away.<br /><br /><br /><span style="font-size: small;"><b>Why Do Chocoholics Come in Threes?</b></span></span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">I wrote about the <a href="http://en.wikipedia.org/wiki/Poisson_distribution">Poisson distribution</a> before, back when I was <a href="http://oatzy.blogspot.co.uk/2011/01/model-cafe.html">modelling cafes</a>. Basically, while we might see an average of one person per time interval, we don't expect to see them so evenly spaced out. Rather, people will arrive in 'bursts' of varying size. This behaviour is described by the Poisson distribution.<br /><br />So I wrote my simulation in Python, using the <a href="http://www.numpy.org/">numpy</a> library for the Poisson distributed random numbers. The results look something like this</span><br /><div class="separator" style="clear: both; text-align: center;"><span style="font-family: Arial,Helvetica,sans-serif;"><a href="http://4.bp.blogspot.com/-KI-wkiE1LSM/UrcPHJcfAtI/AAAAAAAABH0/c1Ss813wLBI/s1600/chocolate-pois.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-KI-wkiE1LSM/UrcPHJcfAtI/AAAAAAAABH0/c1Ss813wLBI/s1600/chocolate-pois.png" /></a></span></div><span style="font-family: Arial,Helvetica,sans-serif;">In this case, the initial decrease is less linear than in the simple model, but the overall decay times are roughly the same (except for the blue line above). This is what we'd expect since, in the above, I chose the average number of people per interval to be 1.<br /><br />So yeah. <a href="https://dl.dropboxusercontent.com/u/4635169/chocolates.py">Try the code</a>, have a play with the parameters, see what happens. The output is the total number of chocolates remaining after each time step.</span><br /><span style="font-family: Arial,Helvetica,sans-serif;"><br /></span><span style="font-family: Arial,Helvetica,sans-serif;">One thing to look at would be how changing the probabilities affects the rate of decay - for example, does the number of chocolates decay faster when all the chocolates are equally popular, or when some are much more popular than others. I suspect the former is the case.</span><br /><br /><span style="font-family: Arial,Helvetica,sans-serif;">It might also be worth adding an option where a person can pick again if their prefered chocolate isn't avaiable. </span><br /><span style="font-family: Arial,Helvetica,sans-serif;"><br /><br /><b>And the IgNobel Prize for Medicine goes to...</b><br /><br />So that's that. And I can definitely see the original paper being nominated for an <a href="http://www.improbable.com/ig/">IgNobel</a>, at the very least.<br /><br />As for me, I have some <a href="http://oatzy.blogspot.co.uk/2011/12/gift-wrapping.html">gift wrapping</a> I really need to get to...<br /><br />Merry Christmas!<br /><br /><br /><i><b>Citation</b></i><br /><br />[1] P R Gajendragadkar, et al. <i>"The survival time of chocolates on hospital wards: covert observational study"</i> <a href="http://www.bmj.com/content/347/bmj.f7198">BMJ 2013;347:f7198</a> (Published 14 December 2013)<br /><br /><br />Oatzy.<br /><br /><br />[Next job, modelling Her Majesty's <a href="http://www.bbc.co.uk/news/uk-25344656">nuts</a>...]</span>Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-35464990455259796802012-12-15T18:29:00.000-08:002012-12-16T06:48:46.686-08:00Is There a Formula for The 'Perfect' Christmas Tree?The other week I was waiting for my tram, reading Metro, and I see this article - <a href="http://metro.co.uk/2012/12/02/treegonometry-formula-for-perfect-christmas-tree-discovered-3150158/">Treegonometry: Formula for perfect Christmas tree discovered</a><br /><br />In other word, a set of equations that will tell you, for example, how much tinsel, or how many baubles are 'required' for the 'perfect' Christmas tree.<br /><br /><a href="http://www.shef.ac.uk/news/nr/debenhams-christmas-tree-formula-1.227810">Here</a> is the press release, with the actual formulas. <br /><br />Now, I have a few issues with this. For one thing, it's a bit silly. Surely the perfect tree is a matter of personal preference, and in that sense can't be defined by a set of mathematical rules.<br /><br />Plus, it seems kind of arrogant to declare that their way is the 'perfect' way, and that (implicitly) any other way is wrong.<br /><br />Also, the fact that the equations are preposterously simple (<i>perfect thing = constant*tree height</i>) makes me suspicious of them. But, worse than that, it really bothers me that I have no idea how they were derived. <br /><br />But I'm starting to rant.<br /><br />The equations in question were actually from a 'study' by the Sheffield University Maths Society (student from my university), and were commissioned by Debenhams (department store). So, I dunno, maybe I'm just bitter that no-one's ever commissioned me to do maths.<br /><br />Anyway. If they'd said that these were equations for an 'ideal' Christmas tree, then I'd consider that more reasonable. You can say, for example, that the ideal Christmas tree should strike a balance between too many and too few decorations, etc.<br /><br />So, to that end, I'm going to have a crack at deriving some, more general, '<i>Equations for an Ideal Christmas Tree</i>' of my own.<br /><br /><br /><b>Lights</b><br /><br />Before we start, there's one simplification we need to make - we will assume that Christmas trees can be approximated to a smooth, regular, right-circular cone - height h, and base radius r.<br /><br />There's going to be a certain degree of error as a result of this approximation, but I'm not going for perfect.<br /><br />Okay. So, for lights, there's actually a justifiable basis for an ideal - the ideal tree should have its lights evenly distributed.<br /><br />Or, to put it another way, you ideally want to wrap your lights such that you have a (roughly) constant light surface density (lights per unit surface area); because you don't want your tree to be covered in clumps and bald spots.<br /><br />This seems like a vague point; how do you decide on the correct 'density'?<br /><br />Well, we actually have two constraints to work from: firstly, we have to wrap a single line around our conical shape, and second, the distance (s) between each pair of adjacent lights on a string will be fixed.<br /><br />From this, we can layout a (hypothetical) grid on the surface of the tree, so that all the lights are equally spaced out. <br /><br />So, how do we do that?<br /><br />1) Draw yourself a cone (tree)<br />2) Draw a grid<br />3) Draw lights at the points where the grid lines meet<br />4) Draw in the proper row lines - these represent the actual string of lights<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-IlLWlA84vGg/UM0ngiySqaI/AAAAAAAABFQ/MoRkau4HTeQ/s1600/tree-grid.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="181" src="http://3.bp.blogspot.com/-IlLWlA84vGg/UM0ngiySqaI/AAAAAAAABFQ/MoRkau4HTeQ/s400/tree-grid.jpg" width="400" /></a></div>In this cases, I've drawn the grid a little tight, so you'd probably want two strings of lights to get this layout (hence the two colours).<br /><br />And if you like, you can play with the distances between rows, and the angles, to get some slightly different layouts <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/--95csqflyKU/UM0tTEgniVI/AAAAAAAABF8/AUJJwsXdAHw/s1600/tree-lights.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="153" src="http://4.bp.blogspot.com/--95csqflyKU/UM0tTEgniVI/AAAAAAAABF8/AUJJwsXdAHw/s400/tree-lights.jpg" width="400" /></a></div>The best grid to try for would be a triangular grid, where the distances between each light and the six nearest are the same<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-FR4lkce-Dxg/UM0uKay8XoI/AAAAAAAABGE/s4orPjMWUBw/s1600/triangle-grid2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-FR4lkce-Dxg/UM0uKay8XoI/AAAAAAAABGE/s4orPjMWUBw/s1600/triangle-grid2.jpg" /></a></div>So, let's say we've picked a grid. How many light will we need?<br /><br />There's a nice short-cut to working this out, without having to worry about lengths of spirals on conic surfaces. To do this, we take advantage of the regular grid layout to work out the lights density (lights per unit area).<br /><br />Each section of grid is roughly a diamond, with all sides about the same length<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-ABmHvHX4-x8/UM0qjZQImdI/AAAAAAAABFg/jOc4zt3NcTg/s1600/diamond.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-ABmHvHX4-x8/UM0qjZQImdI/AAAAAAAABFg/jOc4zt3NcTg/s1600/diamond.jpg" /></a></div>So with a bit of trigonometry, you can get an approximate equation for the area of this diamond<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-juWHs-3vWjA/UM0dEhL5LTI/AAAAAAAABDs/0Sd2F3H3hhg/s1600/ad.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-juWHs-3vWjA/UM0dEhL5LTI/AAAAAAAABDs/0Sd2F3H3hhg/s1600/ad.png" /></a></div>Then, the density is one over that area (since there's one light per grid diamond).<br /><br />We then multiply this density by the total <a href="http://en.wikipedia.org/wiki/Cone#Surface_area">conic surface area</a> (excluding the base) to get the total number of lights needed<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-nNDtsMDL4Qk/UM0daJt1Q_I/AAAAAAAABD0/lkshwjaEMac/s1600/124319_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-nNDtsMDL4Qk/UM0daJt1Q_I/AAAAAAAABD0/lkshwjaEMac/s1600/124319_0.png" /></a></div>And, finally, multiplying that by the separation between lights (s), we get the total length needed:<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-ir78dJKwQZM/UM0aq0REbkI/AAAAAAAABDM/reSCbhye06A/s1600/124317_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-ir78dJKwQZM/UM0aq0REbkI/AAAAAAAABDM/reSCbhye06A/s1600/124317_0.png" /></a></div>Easy. Though, somewhat more complicated than the <i>L = pi*h</i> equation, derived by SUMS. <br /><br />So, then, for the triangular grid (theta=60), you get<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-hpdP4c55oE0/UM0cJiaVOcI/AAAAAAAABDU/3bUIr9jna6M/s1600/ll60.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-hpdP4c55oE0/UM0cJiaVOcI/AAAAAAAABDU/3bUIr9jna6M/s1600/ll60.png" /></a></div>Cool.<br /><br />The only problem then is getting the lights to line up on a grid on the actual tree. So... good luck with that.<br /><br />Realistically, all of this is mostly irrelevant anyway - you can't go out and ask for, say, exactly 5.83m of lights; you buy your lights in pre-cut lengths. Can't get hold of the 'perfect' length of light? Then your tree is imperfect, and you should feel bad.<br /><br />Anyway. The point is, if you can get your lights more or less even - so that there aren't any clumps or bald spots -, then as far as I'm concerned, you're on to a winner.<br /><br /><br /><b>Tinsel</b><br /><br />What's interesting about their equation for tinsel is this factor of 13/8. Now, again, I don't know how these equations were derived, so I don't know if this was intentional; But, 8 and 13 are consecutive Fibonacci numbers. Why is this important? Well, the <a href="http://en.wikipedia.org/wiki/Fibonacci_number">Fibonacci sequence</a> is closely related to spirals.<br /><br />In particular, there's this thing you see in nature; for example, if you look at the spirals on a pineapple, the spirals going in one direction might be 13 and the number in the opposite direction might be 8. Or the numbers might be 21 left and 13 right... The point is, the numbers of spirals on a pineapple are always consecutive Fibonacci numbers (or sometimes <a href="http://en.wikipedia.org/wiki/Lucas_number">Lucas numbers</a>).<br /><br />And you get the same effect on other things, like the spirals of seeds in the head of a sunflower, or the seeds on a strawberry, or the spirals on a pinecone, or a cauliflower, or all sorts of things. Hell, maybe the branches on a Christmas tree form Fibonacci spirals.<br /><br /><a href="http://www.youtube.com/watch?v=ahXIMUkSXX0">Vi Hart</a> explains it better than me. <br /><br />So maybe that has something to do with that pre-factor. Or maybe not. It's an interesting tidbit, though.<br /><br />Anyway.<br /><br /><br />The thing with tinsel is different people like to do tinsel differently - some like to elegantly drape it across the outer branches, others like to wrap up their tree light they're restraining a hostage. It's a matter of preference. But it's going to affect the amount of tinsel you'll need.<br /><br />Where tinsel differs from lights is, you're not trying to set up a grid, or get an even surface density. Rather, in this case, you'd probably want to wrap it such that the rows are more horizontal, with a roughly constant vertical separation.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-LoOHkEuPEGk/UM0pghvG0xI/AAAAAAAABFY/CrqA9T3K-0o/s1600/tinsel.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="http://1.bp.blogspot.com/-LoOHkEuPEGk/UM0pghvG0xI/AAAAAAAABFY/CrqA9T3K-0o/s200/tinsel.jpg" width="131" /></a></div>Here's where things get messy; the <a href="http://mathworld.wolfram.com/ConicalSpiral.html">equation</a> for the length of a spiral on the surface of a cone is given by<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-q0PiASZTyuY/UM0gXNDPM8I/AAAAAAAABEQ/N55tV2SgiBY/s1600/124327_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-q0PiASZTyuY/UM0gXNDPM8I/AAAAAAAABEQ/N55tV2SgiBY/s1600/124327_0.png" /></a></div>I know, right? Maybe you would be better using the SUMS equation for this one.<br /><br /><br /><b>Extras</b><br /><br />The star/angel is going to be some fraction of the height of the tree.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-U0uch6jOlJc/UM0cVa5UdrI/AAAAAAAABDc/Xffxm6496Gs/s1600/hs.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-U0uch6jOlJc/UM0cVa5UdrI/AAAAAAAABDc/Xffxm6496Gs/s1600/hs.png" /></a></div>I don't know what the ideal value of the fraction (alpha) would be, but the 10th they came up with seems reasonable.<br /><br />Baubles, I haven't a clue how they came up with those numbers. The factor of sqrt(17) makes me think some geometry was probably involved, but I dunno.<br /><br />You would probably want to figure it out as some ideal ornament surface density, Db (like with the lights). In this case, the number of baubles needed would be something like <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-MAE4oXy3ssM/UM0cbY7tuTI/AAAAAAAABDk/_28_yJeVeUo/s1600/nb.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-MAE4oXy3ssM/UM0cbY7tuTI/AAAAAAAABDk/_28_yJeVeUo/s1600/nb.png" /></a></div>In fact, if your baubles are all, more or less, the same, you can lay them out on a grid, like the lights. Though, this time, you'd want to have them a little more spaced out, since baubles are much bigger than fairy-lights. But at least this time you don't have the separation constraint.<br /><br /><br /><span style="font-size: small;"><b>Thoughts</b></span><br /><br /><a href="http://www.badscience.net/2006/12/mediaslut-ideas-money-corporatewhore/">Here</a> <a href="http://www.badscience.net/2007/09/imaginary-numbers/">are</a> <a href="http://www.badscience.net/2007/09/clarion-communications-respond-on-the-rigged-jessica-alba-wiggle/">various</a> <a href="http://www.badscience.net/2008/08/fame/">things</a> Ben Goldacre, of Bad Science, has said on the subject of commissioned, 'perfect' formulas. <a href="http://www.guardian.co.uk/science/2009/sep/02/perfect-formula-festival-science">Here</a> is an article by mathematician, Simon Singh. <a href="http://news.bbc.co.uk/1/hi/magazine/3794419.stm">Here</a> is an article on BBC News. And <a href="http://www.andrewt.net/blog/the-perfect-formula/">here</a> is a collection of such formulas on Apathy Sketchpad.<br /><br /><br />To be honest, I wouldn't bother with any of this; their equations, or mine. I mean, would you really want a tree that was 'perfectly' decorated? Cold and artificial are the words that come to mind.<br /><br />And, frankly, I'm not sure my equations would actually work in practice.<br /><br />I'd say, use your best judgement on how much of everything you'll need, and just do your own thing. Have fun with it!<br /><a href="http://2.bp.blogspot.com/-yYUQdoHMIFQ/UM0j6e3N-nI/AAAAAAAABEs/AJ8bfuC39HE/s1600/tree.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="320" src="http://2.bp.blogspot.com/-yYUQdoHMIFQ/UM0j6e3N-nI/AAAAAAAABEs/AJ8bfuC39HE/s320/tree.jpg" width="128" /></a><br /><br />Our Christmas tree is imperfect. In fact, it's gloriously imperfect. No, seriously, it's a mess.<br /><br />My ex's family used to construct these massive, elaborate, works-of-art trees; with yearly colour schemes, and matching baubles, and everything. By comparison, she described our tree as kitsch.<br /><br />But it's adorned with all the baubles, and tinsel, and decorations we accumulated over the last 20-odd years; at least, the ones that haven't been lost or broken. And, in a sentimental sort of way, it is perfect.<br /><br />Well, okay, not perfect. But, damn it, it's ours.<br /><br /><br /><br /><br /><br />Oatzy.<br /><br /><br />[<i>I want you to know, I had no part in decorating that tree.</i>]<br /><br />[<i>...And, yes, that's a <a href="http://oatzy.blogspot.co.uk/2010/12/weeping-angel-christmas-tree-topper.html">weeping angel</a> on top.</i>]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com4tag:blogger.com,1999:blog-14769935.post-72313002869124111912012-09-29T16:53:00.000-07:002012-09-29T16:53:58.736-07:00Picking a Seat on the TramWhen I'm at university, I have to get the tram twice a day. Now I quite like the trams in Sheffield (at least, more than the trains and buses). But if there's anything I can do to get a little more leg room or personal space, then I'm going to try it.<br /><br />On the way in to university, I get on the tram at the start of the line - i.e. the tram is more or less empty when I get on, so I have pretty much free choice of where I sit. In the main, middle section of the tram, seats are grouped into fours - two forwards, two backwards, face to face - and there are there are 5 rows and 2 columns of these groups.<br /><br />(The are more seats in the front and rear carriages, but with difference layouts. And I almost never sit in those sections, so I'm ignoring them.)<br /><br />When the tram's busy, you have to accept that you're probably going to be squashed, and bumping knees with the person across from you, and desperately trying to avoid awkward eye-contact. But when it's less busy, there's the chance for a bit of personal space. <br /><br />The question is, where's the best place to sit (assuming you have free choice) so you're more likely to get some of that personal space?<br /><br />Truth be told, I don't actually know. I figured, probably the best place is the forward-facing window seat in either of the 3rd (middle) row groups.<br /><br />The logic was that a person would either have to sit next to you, or so they're facing backwards - generally less preferable options. And since people are more likely to grab seats nearest doors, the 3rd row is 'best' because it's equidistant from both doors.<br /><br />Of course, if the front and rear doors aren't used equally, then the choice of row would have to be tweaked.<br /><br /><br />From that, I started thinking about how people chose which seat to take in a given four-group. And since I have nothing better to do than stare out the window for the 20-odd minute journeys, I figured I'd try to model it.<br /><br />From empty to full there are 16 possible seat use configurations ('states'). People chose seats based on personal preferences, as well as which seats are already taken. So, for the model, we look at the different states, and consider how they might change with the addition or removal of various numbers of people.<br /><br />Here's an example, that I sketched, of possible transitions resulting from the addition of one person at a time<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-dmxxEbeYpDQ/UGd9lPN3meI/AAAAAAAABAU/N3iWL5Nkucg/s1600/IMG_20120929_155415.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="173" src="http://2.bp.blogspot.com/-dmxxEbeYpDQ/UGd9lPN3meI/AAAAAAAABAU/N3iWL5Nkucg/s320/IMG_20120929_155415.jpg" width="320" /></a></div>The red lines are possible changes of state where the number of people stays the same (usually after the removal of others). More on that later.<br /><br /><br />Now, the key thing here is that different states, and different transitions are more likely to occur than others.<br /><br />For eample, a person with free choice is more likely to chose a forward-facing seat (some people tend to feel unwell traveling backwards), and a window-side seat so they've got something to stare at; unless they're planning on making a hasty exit.<br /><br />So that would theoretically make the backward-facing aisle seat the least popular.<br /><br />HOWEVER, if someone's already sat in FFWS, then BFAS becomes the more preferable, since it avoids having to sit next to, or directly in front of, some random stranger whose just staring blankly out the window in an unsettling sort of way. Also, personal space.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-PiuoiokFohg/UGd-Zbqvv8I/AAAAAAAABAc/ISXQ7Qi_M4w/s1600/IMG_20120929_160205.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="http://2.bp.blogspot.com/-PiuoiokFohg/UGd-Zbqvv8I/AAAAAAAABAc/ISXQ7Qi_M4w/s320/IMG_20120929_160205.jpg" width="320" /></a></div>And from there, if you introduce another person, they're most likely to sit FFAS, since BFWS is a bit awkward to get to and leaves you feeling kind of boxed-in. Plus you have the extra arm room on the aisle-side, and, if necessary, you can turn slightly to give yourself a little more leg room.<br /><br />And if you add a four person, they have a hell of a time getting to the free seat, but it's their only option. Unless BFAS moves across to the window.<br /><br />Now, back to those red lines in the diagram. From the 3-config described above, say BFAS leaves. You now have two people sat next to each other. The person in FFAS may then chose to move to BFAS for the sake of more personal space. This is kind of like how physical systems tend towards their lowest energy (least socially awkward) state.<br /><br /><br />The model itself is actually pretty straight-forward. What you do is construct a <a href="http://en.wikipedia.org/wiki/Stochastic_matrix">stochastic matrix</a> (sometimes called a transition matrix) - basically, a 16x16 'table' that tells you the probability of the seating changing from one state to another. For example<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-Ts9jA6SzG18/UGd_lVy7nOI/AAAAAAAABAk/JahTCvl7rBk/s1600/IMG_20120929_155559.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="168" src="http://3.bp.blogspot.com/-Ts9jA6SzG18/UGd_lVy7nOI/AAAAAAAABAk/JahTCvl7rBk/s320/IMG_20120929_155559.jpg" width="320" /></a></div>And this works for all possible transitions between all possible states. <br /><br />The tricky part if determining the probabilities. You could just make random guesses at it. Or, if you were really determined, you could spend a load of time on trams, recording what transitions happen at each stop and how many times they happen. I don't plan on doing either.<br /><br />But say you have your data and you've constructed your matrix, M. You could do a Monte Carlo type simulation using those probabilities. But with a stochastic matrix you can be more precise.<br /><br />See, if you calculate M*M, then the entries are the probabilities of moving from state i to j after two 'stops'. And if you calculate M^n, they you get the transition probabilities for after N stops.<br /><br />And if you add up the entries down each column, j, then you have the probability of being in state j after N stops - that is, you can find the most likely seating configuration after some arbitrary number of stops. You could also use this method to work out the popularity of each seat over all possible states.<br /><br />Which I think is pretty cool.<br /><br /><br />And that's all well and good for the groups. But what if you want to model the whole tram? That's where things get tricky, since you have to model how people chose seat groups, which itself depends on what seats are already taken in groups. <br /><br />Ultimately, it can be modeled the same way, with a transition matrix. Only now, you're working with a 40x40 matrix.<br /><br />Not difficult, per se, but certainly requires a lot of data and number crunching...<br /><br />Come to think of it, if you were going to go to the trouble of observing and counting state transitions, you could just count how many times each seat is sat in over some arbitrarily long period to figure out each seat's popularity. Then you just need to try to get the seat <i>next to</i> the least popular.<br /><br />But counting isn't as fun as modeling.<br /><br /><br />Oatzy.<br /><br /><br />[<i>I wonder what normal people think about on public transport..</i>]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-46570400044203011862012-07-02T14:16:00.001-07:002012-07-02T15:33:19.867-07:00Sharing a Burger Between ThreeThere are several ways to divide a burger evenly between three people.<br /><br />Probably the best is to cut it radially (like a pizza). It can be tricky working out exactly where to make the cuts, but if you can pull it off, then all the pieces will be roughly identical (topping distribution notwithstanding).<br /><br />But for the sake of arguing, lets say you want to divide the burger by making two parallel cuts: Where, then, do you make the cuts so that all three people get the same amount of burger?<br /><br />NB/ This gets quite maths-heavy, so if you're not interested in that sort of thing, feel free to skip right to the end for the solution. <br /><br /><br /><br /><b>Geometry</b><br /><br />For simplicity, we're going to consider the burger as a circle, and make the cuts so that each chunk has the same area. The two cuts are going to be the same distance from, and parallel to, the central axis of the burger, so we only need to consider the position of one of the cuts.<br /><br />Here's the set-up<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-IWG5Nm9CYxM/T_DcxICrBRI/AAAAAAAAA7w/Y-3GiZkM5sw/s1600/diagram-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="http://2.bp.blogspot.com/-IWG5Nm9CYxM/T_DcxICrBRI/AAAAAAAAA7w/Y-3GiZkM5sw/s200/diagram-1.png" width="200" /></a></div>We work out the area of the cut-off as the area of the circular segment, minus the area of the triangle.<br /><br /><br /><i><b>- </b><b>Aside</b></i><i><b>: Radians</b></i><br /><br /><a href="http://en.wikipedia.org/wiki/Radians">Radians</a> are basically an alternative way of measuring angles. For maths and physics they're generally more useful than degrees.<br /><br />They're relatively easy - there are 2pi radians in a full circle, so 2pi radians = 360 degrees<br /><br />1 radian = 180/pi = 57.3 degrees<br />1 degree = pi/180 = 0.017 radians, etc.<br /><div style="text-align: center;"><br /></div><div style="text-align: center;">* * *</div><br />Back to the circle; with angle x in radians, the area of the circular segment is<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-xBppqL1aZMk/T_HEORwJtNI/AAAAAAAAA9o/Lz22WJlrjnU/s1600/area-seg.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-xBppqL1aZMk/T_HEORwJtNI/AAAAAAAAA9o/Lz22WJlrjnU/s1600/area-seg.png" /></a></div>The area of a triangle is half base times height..<br /><br /><br /><i><b>- Aside: Area of the Triangle</b></i><br /><br />We start by splitting the triangle down the middle, so that we have two identical right angle triangles<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-YZdkzwcjvyI/T_Ddca4bUII/AAAAAAAAA74/l2utxfVK2ks/s1600/diagram-triangle.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="110" src="http://3.bp.blogspot.com/-YZdkzwcjvyI/T_Ddca4bUII/AAAAAAAAA74/l2utxfVK2ks/s320/diagram-triangle.png" width="320" /></a></div>The height, l = r*cos(x/2)<br /><br />The base, b = 2*(r*sin(x/2))<br /><br />So the area of the triangle is (l*b)/2 = r^2 sin(x/2)cos(x/2)<br /><br />Finally, use the <a href="http://en.wikipedia.org/wiki/Double_angle#Double-.2C_triple-.2C_and_half-angle_formulae">identity</a> <i>sin(2x) = 2sin(x)cos(x)</i><br />to get<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-AFv7N8RsFXE/T_HEc63Z59I/AAAAAAAAA9w/HonEkSuJ-8U/s1600/area-tri.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-AFv7N8RsFXE/T_HEc63Z59I/AAAAAAAAA9w/HonEkSuJ-8U/s1600/area-tri.png" /></a></div><div style="text-align: center;">* * *</div><br />So the area of the cut-off is <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-85oxSEtDgAc/T_HE5Je9CKI/AAAAAAAAA94/cuOLBuHKzD4/s1600/area-cut.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-85oxSEtDgAc/T_HE5Je9CKI/AAAAAAAAA94/cuOLBuHKzD4/s1600/area-cut.png" /></a></div>and it needs to equal a third the area of the circle = 1/3 pi r^2.<br /><br />So first, we need to find x satisfying<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-N_y7db-n2tM/T_HFHb0e9tI/AAAAAAAAA-A/EOp0hCLz6cY/s1600/eqn-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-N_y7db-n2tM/T_HFHb0e9tI/AAAAAAAAA-A/EOp0hCLz6cY/s1600/eqn-1.png" /></a></div>or<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-OGyqAtIuYFs/T_HFMqzESGI/AAAAAAAAA-I/qmngsTHj2uw/s1600/eqn-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-OGyqAtIuYFs/T_HFMqzESGI/AAAAAAAAA-I/qmngsTHj2uw/s1600/eqn-2.png" /></a></div>Once we have a value for x, we find where to make the cut from<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-pXH9AQYO5cg/T_HFT9UOE1I/AAAAAAAAA-Q/jsPdfsq-6BE/s1600/eqn-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-pXH9AQYO5cg/T_HFT9UOE1I/AAAAAAAAA-Q/jsPdfsq-6BE/s1600/eqn-3.png" /></a></div><br /><br /><b>Intermission</b><br /><br />The thing about this equation is it doesn't have an exact, analytical solution - to find the solution you have to use <a href="http://en.wikipedia.org/wiki/Root-finding_algorithm">numerical methods</a>. Well, I say you have to use numerical methods; these days you can just type the equation into <a href="http://www.wolframalpha.com/">WolframAlpha</a>, and you'll get a solution like *snaps fingers*<br /><br />Which is nice. I even have the WolframAlpha app on my phone. But when I thought up this question I was on holiday in Sherwood forest, where there was literally no mobile singal.<br /><br />So that was out of the question. And since I'm not in the habit of carrying a scientific calculator around with me, I was stuck with the basic calculator on my phone. It looks like this:<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-3jJCMZZk5Ik/T_D0UwqXEgI/AAAAAAAAA9c/AGFAqFo3bQQ/s1600/2012-07-02+01.57.07.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="http://3.bp.blogspot.com/-3jJCMZZk5Ik/T_D0UwqXEgI/AAAAAAAAA9c/AGFAqFo3bQQ/s200/2012-07-02+01.57.07.png" width="140" /></a></div>No trig functions, no square roots, no pi button. It doesn't even do brackets, or have a memory function. Luckily, I am in the habit of carrying around a notepad and pen.<br /><br />Anyway, there are two ways of working this out with only a basic calculator. The first is 'easier', but only if you know some stuff, and the numbers happen to be nice (in this case, they kind of are). The second is harder, in that it requires more number crunching, but it'll work with any numbers, and can be more precise.<br /><br />Again, feel free to skip to the solution if you're not interested in the gritty details.<br /><br /><br /><br /><b>Method One</b><br /><br />First of all, here's a graph of the two sides of the equation<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-vWn9VK4gIx8/T_DvN5UxuxI/AAAAAAAAA9I/3lFMdDM6iLY/s1600/graph-edit.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="186" src="http://4.bp.blogspot.com/-vWn9VK4gIx8/T_DvN5UxuxI/AAAAAAAAA9I/3lFMdDM6iLY/s320/graph-edit.png" width="320" /></a></td></tr><tr align="right"><td class="tr-caption"><span style="font-size: xx-small;">hand-drawn with <a href="http://evernote.com/skitch/">Skitch</a></span></td></tr></tbody></table>We want to find the point at which the two graphs cross. We can see that that happens somewhere between 2pi/3 and pi (120 and 180 degrees). So, lets make a guess that it's exactly halfway between these two values: 5pi/6 (150 degrees).<br /><br />For the right hand side of the equation: 5pi/6 - 2pi/3 = 0.52<br /><br />NB/ I'm using pi=3.1416 (rounded to 4 decimal places). If you prefer, you could use the approximation 22/7. The result should be roughly the same.<br /><br />For the left hand side of the equation, we need to work out sin(5pi/6)<br /><br />At A-level, we were expected to memorise sin() and cos() of angles 0, 30, 45, 60, 90, and 180 (degrees). We were also expected to know the <a href="http://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities">formulas</a> for sin() and cos() of sums of angles. For sin(), it works like<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-AXoIVhvdoHY/T_H98raunFI/AAAAAAAAA-4/cimUhj3pO9M/s1600/sin-mult.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-AXoIVhvdoHY/T_H98raunFI/AAAAAAAAA-4/cimUhj3pO9M/s1600/sin-mult.png" /></a></div>Why is this important? Well 150 degrees = 180 - 30 (5pi/6 rads = pi - pi/6)<br /><br />So<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-kK2QvfwalRk/T_H9Peb_UcI/AAAAAAAAA-w/ELeyYhutHxI/s1600/103171_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-kK2QvfwalRk/T_H9Peb_UcI/AAAAAAAAA-w/ELeyYhutHxI/s1600/103171_0.png" /></a></div>And since 0.5 is pretty close to 0.52 - less than 5% error - we can accept the convenience of that answer and say it's close enough.<br /><br />So our approximate value of x is 5pi/6 <u><i>= 2.618</i></u><br /><br />[Incidentally, the identity for sin(2x) is just a special case of the above, with a=b=x; i.e. sin(2x) = sin(x+x) = 2sin(x)cos(x)]<br /><br /><br />Now we just need to work out l/r = cos(x/2) = cos(5pi/12)<br /><br />For this one, 5pi/12 rads = 75 degrees = 45 + 30, so we can use<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-vFj3e0A-Xxo/T_H-DK0hGkI/AAAAAAAAA_A/S3mYfwqpVFQ/s1600/103172_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-vFj3e0A-Xxo/T_H-DK0hGkI/AAAAAAAAA_A/S3mYfwqpVFQ/s1600/103172_0.png" /></a></div>So<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-ryKX2-JkeVw/T_H_NZs3J_I/AAAAAAAAA_I/z7aY7FPcSmI/s1600/103174_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-ryKX2-JkeVw/T_H_NZs3J_I/AAAAAAAAA_I/z7aY7FPcSmI/s1600/103174_0.png" /></a></div>And we just have to evaluate that. But we don't have a square root button. Now, I just happen to know that sqrt(3) ~ 1.73 and sqrt(2) ~ 1.41. <br /><br />But I'm just weird like that. Let's say you don't. How do you work it out?<br /><br /><br /><i><b>- Aside: Square Roots</b></i> <br /><br />There are <a href="http://en.wikipedia.org/wiki/Methods_of_computing_square_roots">several ways</a> of working out square roots with just basic operators. For two easy examples:<br /><br />The first is '<a href="http://en.wikipedia.org/wiki/Trial_and_improvement">Trial and Improvement</a>' - pick a number, square it, does that give the right answer? If not, pick another number based on whether the last guess was too big or too small.<br /><br />For example: sqrt(3)<br /><blockquote class="tr_bq">1.5 -> 2.25 -> too small<br />1.7 -> 2.89 -> too small<br />1.8 -> 3.24 -> too big<br />1.75 -> 3.0625 -> too big<br />1.73 -> 2.9929 -> too small<br />1.74 -> 3.0276 -> too big<br />1.735 -> 3.010225 -> too big<br />1.7325 -> 3.00155625 -> too big<br />1.732 -> 2.999824 -> too small<br />etc.</blockquote><br />The second method is the "<a href="http://en.wikipedia.org/wiki/Babylonian_method#Babylonian_method">Babylonian Method</a>". It's more systematic, and can converge to the correct answer quicker than guessing. But it can be irritating if your calculator doesn't have a memory function.<br /><br />It uses the recurrence relation<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-wfJBHxb-sgo/T_Dpj8a8uKI/AAAAAAAAA88/tZoJ7V738Yc/s1600/103119_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-wfJBHxb-sgo/T_Dpj8a8uKI/AAAAAAAAA88/tZoJ7V738Yc/s1600/103119_0.png" /></a></div>Basically, you make a guess xn. Divide the number you want to square root (S) by xn. If xn is lower than the actual square root, then S/xn will be greater than it. That means the actual root will be between xn and S/xn, so we make the next guess xn+1 the average of these two values. Repeat until x is sufficiently accurate.<br /><br />For example: sqrt(2)<br /><blockquote class="tr_bq">x0 = 1.5 -> 2/1.5 = 1.33<br />x1 = (1.5 + 1.33)/2 = 1.4166.. -> 2/1.4167 = 1.41176..<br />x2 = (1.4167 + 1.41176..)/2 = 1.41421.. -> 2/1.41421 = 1.41421.. </blockquote><div style="text-align: center;">* * *</div><br />Whatever way you do it, you repeat the process until you get the degree of accuracy you're happy with.<br /><br />You should get the answer around <i><u>l/r = 0.259</u></i><br /><br /><br /><br /><span style="font-size: small;"><b>Method Two</b></span><br /><br />We go back to the equation sin(x) = x - 2pi/3<br /><br />We still have to find the solution numerically, we still don't have a calculator with a sin() function, and this time the numbers don't work out nicely.<br /><br />So, the question is, how do we calculate sin(x)?<br /><br /><br /><i><b>- Aside: Taylor Expansion</b></i><br /><br />The <a href="http://en.wikipedia.org/wiki/Taylor_expansion">Taylor Expansion</a> of a function is a way of fitting a polynomial (sums of powers) to a more complicated function. It works like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-6udstFWxglc/T_Dwn8u5hII/AAAAAAAAA9Q/ezrJg3vVBoM/s1600/103120_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-6udstFWxglc/T_Dwn8u5hII/AAAAAAAAA9Q/ezrJg3vVBoM/s1600/103120_0.png" /></a></div>Basically, it gives a way of converting a function we can't calculate into an infinite sum of powers of x, which we <i>can</i> calculate.<br /><br />It's usually expanded around the origin (x0=0), since the equations work out neater. But you can do it around any point, x0=a. This is useful if the value you are trying to calculate is far from x=0. The closer x is to x0=a, the quicker the sum converges.<br /><br />Even though the expansion is an infinite sum, it's usually sufficient to just take the first few terms, since each additional term makes a smaller and smaller contribution to the sum.<br /><br />So the trick is working out how many terms you need to include to get some desired level of accuracy.<br /><br /><div style="text-align: center;">* * *</div><br />In this case, I'm going to use the Taylor Expansion of sin(x) around x0=pi, since the approximate value (2.6) is nearer to pi than 0. <br /><br />Here's what the expansion looks like<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-Of6DLjadmag/T_DgvAUqL1I/AAAAAAAAA8I/4no0VLe9tRg/s1600/Taylor-sin-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-Of6DLjadmag/T_DgvAUqL1I/AAAAAAAAA8I/4no0VLe9tRg/s1600/Taylor-sin-alt.png" /></a></div><br />So, how many terms do we need to include?<br /><br />Here's what the graph looks like for different numbers of terms<br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-ap72RoFNqYM/T_DhTL1oMyI/AAAAAAAAA8U/sCokx4AsCBU/s1600/taylor-graphs-alt.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="258" src="http://2.bp.blogspot.com/-ap72RoFNqYM/T_DhTL1oMyI/AAAAAAAAA8U/sCokx4AsCBU/s400/taylor-graphs-alt.png" width="400" /></a></td></tr><tr align="right"><td class="tr-caption"><span style="font-size: xx-small;">plotted with <a href="http://www.wolframalpha.com/">WolframAlpha</a></span></td></tr></tbody></table>For a value around 2.6, it can be shown that including the first two terms is correct to ~3 decimal places; the first three terms is correct to ~5 decimal places; the first four terms to ~7 decimal places, etc.<br /><br />So I would probably go to the third term (for 5dp), but only take the result to 3dp.<br /><br />NB/ We shouldn't get too hung up on getting an extremely accurate value for x, since we're already getting rounding errors from the factors of pi in the expansion. Also, since we're calculating x to 5dp, we should use pi=3.14159<br /><br />That means we want to solve <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-a9dR569FY8U/T_DkoGuc15I/AAAAAAAAA8g/2XNqdYt9S7k/s1600/sin-taylor.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-a9dR569FY8U/T_DkoGuc15I/AAAAAAAAA8g/2XNqdYt9S7k/s1600/sin-taylor.png" /></a></div>which can't be solved exactly.<br /><br />So, for finding the correct value (without <a href="http://www.wolframalpha.com/input/?i=5pi%2F3+-+2x+-+%28pi-x%29%5E3%2F3%21+%2B+%28pi-x%29%5E5%2F5%21">WolframAlpha</a>), we can use any <a href="http://en.wikipedia.org/wiki/Root-finding_algorithm">root-finding method</a>. For what it's worth, I used Trial and Improvement; the other methods are easier with a computer.<br /><br />But, note that the function is decreasing<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-B74p_xA0xWw/T_CMS0OCRqI/AAAAAAAAA7k/nncdV9mJ6DI/s1600/graph-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-B74p_xA0xWw/T_CMS0OCRqI/AAAAAAAAA7k/nncdV9mJ6DI/s1600/graph-2.png" /></a></div>So if the guess gives a value greater than zero, you need to <i>increase</i> the value of x (and vice versa).<br /><br />If you run through all that (I won't go into detail), it gives a value around <u><i>x=2.605</i></u><br /><br /><br />Alternatively, you could expand around x0=5pi/6 (if you know/can work-out sin and cos of 150 deg without a calculator).<br /><br />In this case you'd only need up to the term in x^2 (correct to ~4dp). Using this expansion would mean <a href="http://www.wolframalpha.com/input/?i=0.5+-+sqrt%283%29%2F2+%28x+-+5pi%2F6%29+-+1%2F4+%28x+-+5pi%2F6%29%5E2+%3D+x+-+2pi%2F3">solving</a> a quadratic equation, which is <a href="http://en.wikipedia.org/wiki/Quadratic_equation#Quadratic_formula">easy</a>. But using this expansion can introduce more rounding errors from the factors of sqrt(3). It's a matter of preference, I guess. The answer should be about the same.<br /><br /><br />Finally, we need to calculate cos(x/2)<br /><br />Again, we use the Taylor Expansion to calculate cos(). In this case, we're doing the expansion around x0=0; the expansion is <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-LRVhFTQsWyc/T_DmOpSK-lI/AAAAAAAAA8w/DzBlAXf95AA/s1600/103118_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-LRVhFTQsWyc/T_DmOpSK-lI/AAAAAAAAA8w/DzBlAXf95AA/s1600/103118_0.png" /></a></div>In this case, you just keep adding terms until the result remains approximately constant to some desired degree of accuracy (3pd).<br /><br />This gives a value around <u><i>l/r = 0.265</i></u><br /><br /><br /><br /><b>So What is the Real Answer?</b><br /><br />Once I got to somewhere where I could get at WolframAlpha, I checked the real numbers; here are the <a href="http://www.wolframalpha.com/input/?i=sin%28x%29+%3D+x-+2pi%2F3">results</a>:<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-EjFFjJcvU9Q/T_HXSqku2UI/AAAAAAAAA-c/tEOr_1Di81w/s1600/soln-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-EjFFjJcvU9Q/T_HXSqku2UI/AAAAAAAAA-c/tEOr_1Di81w/s1600/soln-1.png" /></a></div>The approximation of x from <i>Method One</i> (2.618) is an over estimate by ~0.5%, which is relatively <a href="http://en.wikipedia.org/wiki/Margin_of_error">acceptable</a>. The approximation from <i>Method Two</i> (2.605) is correct to 3 decimal places, which is definitely acceptable.<br /><br />And for the <a href="http://www.wolframalpha.com/input/?i=cos%282.6053256746%2F2%29">value</a> of l/r<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-ZGkJLkQK7qc/T_HXdfQhWHI/AAAAAAAAA-k/ideFX8A1FCY/s1600/soln-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-ZGkJLkQK7qc/T_HXdfQhWHI/AAAAAAAAA-k/ideFX8A1FCY/s1600/soln-2.png" /></a></div>From <i>Method One</i> (0.259), the approximation is an under estimate by 2%, and correct to 2 decimal places, so is probably acceptable. The approximation from <i>Method Two</i> (0.265) is, again, correct to 3 decimal places. So that is also acceptable.<br /><br />So, if the numbers happen to be convenient and you know some trigonometry, you're probably as well using <i>Method One</i>. If not, or if you just want more accuracy, then go for <i>Method Two</i>.<br /><br /><br /><br /><b>Applying the Results</b><br /><br />The results are actually quite nice, in terms of practical application (dividing up a burger). The ratio of the radius (0.265) being close to one quarter, you find the cuts like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-vAOMNlOp5kA/T_DeG38ZHZI/AAAAAAAAA8A/pQK2sWMSOyA/s1600/diagram-cuts.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="http://1.bp.blogspot.com/-vAOMNlOp5kA/T_DeG38ZHZI/AAAAAAAAA8A/pQK2sWMSOyA/s200/diagram-cuts.png" width="200" /></a></div>That is, find the central axis, then find the (imaginary) line halfway between the centre and the edge - make the cut halfway between the centre and this imaginary line (maybe cut an extra hair's breadth towards the edge). Repeat on the other side.<br /><br />Easy.<br /><br /><br />So, now you know. Obviously, all this applies to dividing any circular thing evenly between three people. You could probably even adapt the methods for sharing between even more people.<br /><br />And in theory, You could do all this with <i>just</i> pen and paper (no calculator). Though you probably wouldn't want to. I know I wouldn't..<br /><br /><br />Oatzy.<br /><br /><br /><span style="font-size: x-small;">[</span><i><span style="font-size: x-small;">Wow, I really managed to stretch that one out</span></i><b><a href="http://en.wikipedia.org/wiki/That%27s_what_he_said">.</a></b><span style="font-size: x-small;">]</span>Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com1tag:blogger.com,1999:blog-14769935.post-3136225686775613452012-06-24T17:07:00.000-07:002012-06-24T17:51:01.275-07:00Too Many LotteriesSo recently I'd gotten really into this TV show - it's called '<a href="http://en.wikipedia.org/wiki/Life_%28NBC_TV_series%29">Life</a>', and it's a 'murder of the week' crime drama starring Damian Lewis. Lewis plays Detective Crews, who was previously in prison for a muder he didn't commit, and he's into zen and such. It's a bit silly. But in an endearing sort of way.<br /><br />Unfortunately, the only way I could watch it (short of buying the DVDs) is to stay up til 4am. Which isn't really a problem; I don't have anything to get up for. Though it is tricky getting to sleep as the sun's coming up.<br /><br />Anyway, the point is, by 4am, there are only, like, four adverts on rotation (on FX, at least). These are: upcoming shows on FX, whatever product JML is flogging, that tacky accident helpline ad with Esther Rantzen, and this '<i>Win Trillions</i>' thing.<br /><br /><i>Win Trillions</i> is a lottery sindicate thing - from what I understand, it works like this: a bunch of people from around the world get together, each buying tickets for their respective countries' lotteries. If one of the sindicate members wins, they split the winnings with their fellow sindicate members (and presumedly, <i>Win Trillions</i> takes a cut).<br /><br />Really, though, the details aren't that important. What is important is their slogan - "play more lotteries, get more chances".<br /><br />Which isn't a false claim. It's just kind of misleading; playinng more lotteries <i>does</i> mean more chances of winning, but only in a similar way as buying more tickets for a single lottery means more chances of winning - your odds of winning are so small to begin with (<a href="http://en.wikipedia.org/wiki/Lottery#Probability_of_winning"><i>~1 in 13,983,816</i></a>) that you'd have to play thousands of lotteries (or buy thousands of tickets) to significantly improve your chances. And there are only so many lotteries in the world.<br /><br /><br /><b>Strategies</b><br /><br />You'll notice I used the word 'similar' back there; the fact is, buying more tickets, and playing more lotteries don't 'improve' odds in the same way.<br /><br />So the question is, which is the better strategy - buying many tickets for a single lottery, or buying one ticket for each of several lotteries?<br /><br />First, imagine I have a dice; I'm going to roll the dice, and you have to bet on the outcome. Your choice is this - would you rather place three bets on one dice roll, or one bet on each of three dice rolls?<br /><br />For the first case, say you place your bets on it being 1, or 2, or 3. Your odds of guessing the correct number (and winning) is 3 in 6 (=0.5).<br /><br />For the second case, say you bet that each of the rolls will be 6. Then your odds of winning 'something' is odds of winning just one of the dice rolls, plus the odds of winning just two of the dice rolls, plus the odds of winning all three.<br /><br />Which is trickier to work out. For example, the odds of winning the first roll only, is odds of getting the first roll right (1 in 6), <i>and not</i> getting the second roll right (5 in 6) <i>and not</i> getting the third roll right (5 in 6) - which makes the probability of getting just the first roll right = 1/6 * 5/6 * 5/6 = 25 in 216<br /><br />And you have to work out the probabilities for all possible winning outcomes (in this case, there are seven winning outcomes, one losing.).<br /><br />Alternatively, we can use the trick that the odds of winning <i>something</i> is one minus the odds of winning <i>nothing</i>. The odds of winning nothing is the odds of getting all the bets wrong: 5/6 * 5/6 * 5/6 = 125 in 216 (0.58)<br /><br />So the odds of winning something is 91 in 216 (=0.42)<br /><br />So for this dice game, it's better to place your three bets on a single dice roll.<br /><br />But what about in general?<br /><br />We say each game has a probability <i>p</i> of winning, and that we're going to place <i>n</i> bets (with n>1).<br /><br />For the single game bet, the probability of winning is <i>n*p</i><br /><br />For the multi-game bet, the probability of winning is <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-4WdGfZ6dJz0/T-eaX16w4oI/AAAAAAAAA5Y/Kmkx2ID0tRY/s1600/102525_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-4WdGfZ6dJz0/T-eaX16w4oI/AAAAAAAAA5Y/Kmkx2ID0tRY/s1600/102525_0.png" /></a></div><br /><b>Which is bigger?</b><br /><br />Well for n = 1, the games are idential. For n = 2, the single game is <i>2p</i>, the multi-game is <i>2p - p^2</i>; the single game has higher odds. For n >= 1/p, the single game gives a probability greater than or equal to 1 - i.e. guaranteed win - where the multi-game always gives a values less than one; again, the single game gives better odds.<br /><br />So it's looking like the single game is always better. But how do we prove it?<br /><br />For this, we look at the <a href="http://en.wikipedia.org/wiki/Binomial_expansion">binomial expansion</a> of (1-p)^n<br /><br />A Binomial Expansion works like this <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-QDfxgxt1hkg/T-ecx4Zh3xI/AAAAAAAAA5g/oulCSRKVzK0/s1600/102527_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-QDfxgxt1hkg/T-ecx4Zh3xI/AAAAAAAAA5g/oulCSRKVzK0/s1600/102527_0.png" /></a></div>where <i>k!</i> is the <a href="http://en.wikipedia.org/wiki/Factorial">factorial</a> of k. For example: 2! = 2*1; 3! = 3*2*1; 4! = 4*3*2*1; etc.<br /><br />In this case, x = 1 and y = -p, so the expansion becomes <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-N9jeBaJvP6w/T-edWRJNVII/AAAAAAAAA5o/gGNGugG-7Kk/s1600/binomial-expansion-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-N9jeBaJvP6w/T-edWRJNVII/AAAAAAAAA5o/gGNGugG-7Kk/s1600/binomial-expansion-alt.png" /></a></div>Which makes the probability of winning the multi-game<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-jh0lqhDrJ_E/T-edgQ3BhAI/AAAAAAAAA5w/5rl7utjgzec/s1600/p-multi-expanded.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-jh0lqhDrJ_E/T-edgQ3BhAI/AAAAAAAAA5w/5rl7utjgzec/s1600/p-multi-expanded.png" /></a></div>So whether or not the mutli-game has higher odds than the single game depends on the sum of the terms after np.<br /><br /><br />First, we compare the k-th and (k+1)-th (adjacent) terms in the expansion<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-Sy1Sk7AAnbw/T-edqCKVOgI/AAAAAAAAA54/X_YIyxEBXD4/s1600/compare-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-Sy1Sk7AAnbw/T-edqCKVOgI/AAAAAAAAA54/X_YIyxEBXD4/s1600/compare-1.png" /></a></div>Which simplifies to<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-_9uBlqTf0Xo/T-edvle7p6I/AAAAAAAAA6A/FXec--NNaLk/s1600/compare-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-_9uBlqTf0Xo/T-edvle7p6I/AAAAAAAAA6A/FXec--NNaLk/s1600/compare-2.png" /></a></div>The righthand side has it's maximum value when n is maximum. We previously defined the maximum value of n as <i>1/p</i>, so<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-zPUY5pkope8/T-ed5lB1ZzI/AAAAAAAAA6I/UbSdHL_ayA4/s1600/compare-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-zPUY5pkope8/T-ed5lB1ZzI/AAAAAAAAA6I/UbSdHL_ayA4/s1600/compare-3.png" /></a></div>since p and k are always greater than 0.<br /><br />Which means<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-MXdSaJDzMUA/T-eeH28pBEI/AAAAAAAAA6Q/lezBz0DlBRY/s1600/compare-4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-MXdSaJDzMUA/T-eeH28pBEI/AAAAAAAAA6Q/lezBz0DlBRY/s1600/compare-4.png" /></a></div>In other words, each term in the expansion is smaller than the ones before it.<br /><br />This means that, if we pair up terms, <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-_xdaA0SAMp8/T-efcXPYM0I/AAAAAAAAA6Y/TBalZKtKcik/s1600/102534_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-_xdaA0SAMp8/T-efcXPYM0I/AAAAAAAAA6Y/TBalZKtKcik/s1600/102534_0.png" /></a></div>i.e. the sum of each pair will be negative. Therefore, the sum of all the pairs (all the terms in the expansion after np) is negative. So necessarily<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-uoocYQKai9c/T-egVmly7RI/AAAAAAAAA6g/q7qppZCMIHI/s1600/102537_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-uoocYQKai9c/T-egVmly7RI/AAAAAAAAA6g/q7qppZCMIHI/s1600/102537_0.png" /></a></div>Which means the <b><i>odds of winning the single game is ALWAYS better than the odds of winning the multi-game</i></b>.<br /><br /><br /><b>Pay-Outs</b><br /><br />You might have noticed that the single game has a single possible payout, where playing the multi-game means you could win double, triple, or even more of the prize. So the odds of winning the multi-game are lower, but you stand to win more.<br /><br />Does that mean it's worth the extra risk?<br /><br />For this, we look at the expected winnings for each game. The expected winnings is calculated as the <i>probability of winning</i> multiplied by the <i>prize amount</i>.<br /><br />So if you were guessing the outcome of a dice roll, and the prize was £1, then the expected winnings from playing that game would be ~17p.<br /><br />Or think of it like this - if you had to guess the outcome of a dice roll six times, you would expect to be right once out of six. So your expected prize for 6 rolls is £1, or 17p per roll.<br /><br />For this single game, the payout is easy: <i>w*n*p</i> (where w is the prize for winning one game)<br /><br />For the multi-game it's more complicate. We can't use the same trick as last time; instead, we have to work out odds of winning one game times prize from one game + odds of winning two games times prize from two game + etc.<br /><br /><br />The general form is the series sum<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-JYu5wWx3VCo/T-eiI80sDDI/AAAAAAAAA6o/yDBw6YGmuEw/s1600/utility-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-JYu5wWx3VCo/T-eiI80sDDI/AAAAAAAAA6o/yDBw6YGmuEw/s1600/utility-1.png" /></a></div>Which can be manipulated and simplified to give<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-Q6WkpIKvMt0/T-eiYpE6xII/AAAAAAAAA6w/63kmkyA6E6M/s1600/utility-1a.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-Q6WkpIKvMt0/T-eiYpE6xII/AAAAAAAAA6w/63kmkyA6E6M/s1600/utility-1a.png" /></a></div>Now, if we expand it out again<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-e6v18VFazI0/T-ei08KB9tI/AAAAAAAAA64/KFWml0s5zN4/s1600/utility-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-e6v18VFazI0/T-ei08KB9tI/AAAAAAAAA64/KFWml0s5zN4/s1600/utility-2.png" /></a></div>Then there's a factor of np in each term, so we can take that outside the bracket<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-LN6BmDkgMfs/T-ejAjTkurI/AAAAAAAAA7A/utXeX2RQyXI/s1600/utility-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-LN6BmDkgMfs/T-ejAjTkurI/AAAAAAAAA7A/utXeX2RQyXI/s1600/utility-3.png" /></a></div>And since n is some arbitrary interger, we can substitute m = n - 1 and put it back in summation form<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-dUaoBR-t6k0/T-ejO4wULoI/AAAAAAAAA7I/lUqnAM513gs/s1600/utility-4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-dUaoBR-t6k0/T-ejO4wULoI/AAAAAAAAA7I/lUqnAM513gs/s1600/utility-4.png" /></a></div>Now, you might notice the summation part is actually a binomial expansion; in this case we have x = (1 - p) and y = p, so<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-UGLC69axCWI/T-ekPt7boLI/AAAAAAAAA7Q/IIIXei_8VXg/s1600/102561_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-UGLC69axCWI/T-ekPt7boLI/AAAAAAAAA7Q/IIIXei_8VXg/s1600/102561_0.png" /></a></div>For all values of p and m = n - 1.<br /><br />Therefore, the expected payout of the multi-game is always <i>w*n*p</i> <br /><br /><br />You'll notice that this is the exact same expected payout as for the single game. As in, <b><i>regardless of which strategy you use, your expected payout is the same</i></b>.<br /><br />So the question is, would you rather a low risk low prize game, or a high risk (potentially) high prize game?<br /><br /><br /><b>Complexities</b><br /><br />You'll notice for this I assumed all lotteries have the same odds of winning and the same prize amount. This is not necessarily true.<br /><br />Even so, from this, the best strategy would be to find the lottery with the best single game single bet expected payout (prize * probability of winning), and buy several tickets for that game, rather than spread your money around.<br /><br />The other thing in lotteries is you can usually win smaller prizes for matching fewer numbers. But I don't imagine that changes which strategy is best. <br /><br />So now you know. For what it's worth. The odds of winning playing, say, 70 tickets on one lottery is only about two 10,000ths of a percent better than playing one ticket on 70 lotteries.<br /><br /><br />Incidentally, those derivations up there count as <a href="http://en.wikipedia.org/wiki/Mathematical_proof">mathematical proofs</a>. So these conclusions I've drawn are irrefutable<i>*</i>. And the results apply to any game where you're placing bets on a single probability random outcome - not just lotteries. <br /><br /><br />Oatzy.<br /><br /><br />[<i>*-</i> Assuming I haven't cocked it up...]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-39967650154936267792012-05-29T18:23:00.000-07:002012-05-30T11:29:02.953-07:00Sexuality and Population DynamicsSo I was reading <a href="http://misformaddie.tumblr.com/post/21092347758">this</a> response to a <a href="http://www.dailymail.co.uk/debate/article-2129340/Gay-cure-bus-advert-Homosexuality-IS-departure-norm.html#ixzz1s1f6TPCa">Daily Mail opinion piece</a> about those 'gay cure' bus adverts, and I see this quote (from the Mail article)<br /><blockquote class="tr_bq">“<i>Since only about one percent of us are [gay], homosexuality is obviously a departure from the norm... Reversing that proportion would spell the end of the human race, which is clearly undesirable.</i>”</blockquote>There are several things wrong with this statement - some of which the response, linked above, points out.<br /><br />First of all, that 1% values. The actual number is a tricky figure to pin down. The UK Office of National Statistics puts the figure at 1.5% gay or bisexual; the linked response lists 3 studies which put the value around 7% (for the UK), and various other <a href="http://en.wikipedia.org/wiki/Demographics_of_sexual_orientation">studies</a> suggest number in the modern West is somewhere between 2-13%.<br /><br />Secondly, just because something is (statistically) "a departure from the norm", doesn't mean it's bad - for example, ~10% of people are <a href="http://en.wikipedia.org/wiki/Left-handedness">left-handed</a>, ~2% of people have <a href="http://en.wikipedia.org/wiki/Ginger_hair">red hair</a>, etc. (<a href="http://en.wikipedia.org/wiki/Bias_against_left-handed_people">Historical</a> <a href="http://en.wikipedia.org/wiki/Ginger_hair#Prejudice_and_discrimination_against_redheads">beliefs</a> notwithstanding.)<br /><br /><br />But all that's tangential. What I'm interested in is the second part - would a majority gay population spell the end of humanity?<br /><br />[NB/ for the remainder of the blog, I shall be using 'gay' colloquially, as shorthand for all of LGBTQUA, etc.]<br /><br /><br /><b>The Simple Model</b><br /><br />It's an interesting <a href="http://en.wikipedia.org/wiki/Population_dynamics">population dynamics</a> question, and it's easy to model if we make some assumptions and simplifications:<br /><br />1) We assume that homosexuals don't reproduce at all (either directly or indirectly). This isn't true - but it seems to be a common belief.<br /><br />2) We assume that the rest of the population reproduces with some fixed <a href="http://en.wikipedia.org/wiki/Population_growth">rate</a> (<i>b=0.02059</i>), which doesn't change from year to year. Similarly, we assumes a fixed death rate (<i>d=0.00812</i>), which affects all members of the population equally.<br /><br />3) We assume that sexuality is binary, definite and static, with a fixed incidence of homosexuality, <i>g = 0.07</i> - that is, of all children born 7% will be gay.<br /><br /><br />So the model we're using is preposterously simplified - effectively,<br /><br /><div style="text-align: center;"><i>new population = old population + births - deaths</i></div><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-HZqbNNizH8A/T5NT79euMbI/AAAAAAAAA04/QbARAJdaVtA/s1600/model-flow.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="236" src="http://3.bp.blogspot.com/-HZqbNNizH8A/T5NT79euMbI/AAAAAAAAA04/QbARAJdaVtA/s320/model-flow.png" width="320" /></a></div>And we can easily program this model and run simulations.<br /><br />Now, there are two ways to interpret the article's statement in the context of our model -<br /><br /><br />1) "if 99% of the (initial) population were gay" assuming the 7% incidence<br /><br />The outcome looks like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-kxh4GB7DIJ8/T5NV28gwxXI/AAAAAAAAA1A/Ff5xbvFgs1I/s1600/plot-g07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="161" src="http://1.bp.blogspot.com/-kxh4GB7DIJ8/T5NV28gwxXI/AAAAAAAAA1A/Ff5xbvFgs1I/s320/plot-g07.png" width="320" /></a></div>In this case, the population drops at first, but then starts to grow again, exponentially - certainly not the end of the human race. Also notable is that the proportion of the population that is gay stabilises at 7% (as expected).<br /><br />2) "if 99% of people were born gay"<br /><br />Now, the outcome looks like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-tLoTuL-0HMU/T5NWgeXRETI/AAAAAAAAA1I/05AU1EAQKfE/s1600/plot-g99.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="173" src="http://2.bp.blogspot.com/-tLoTuL-0HMU/T5NWgeXRETI/AAAAAAAAA1I/05AU1EAQKfE/s320/plot-g99.png" width="320" /></a></div>Exponential decay - so, in this case, humanity would die out. But if 99% of the population isn't reproducing, what do you expect?<br /><br />Here are the <a href="http://en.wikipedia.org/wiki/Phase_portrait">phase portraits</a> of the above two cases, for a slightly different view of what's happening (gay population along x, straight population along y)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-VN1BdG96GD0/T8QdrPHNbhI/AAAAAAAAA4A/x_4Kv6I-s10/s1600/phase-portraits-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="208" src="http://4.bp.blogspot.com/-VN1BdG96GD0/T8QdrPHNbhI/AAAAAAAAA4A/x_4Kv6I-s10/s400/phase-portraits-alt.png" width="400" /></a></div>Again, for <i>g=0.07</i> the population tends to infinity; for <i>g=0.99</i> the population tends to zero. Note that this behaviour is (ultimately) the same for all initial population ratios.<br /><br /><br /><b>What I Learned in MAS271</b><br /><br />We don't actually need numerical simulations to study the behaviour of this (or similar) systems - the model can be solved analytically.<br /><br />First of all, we generalise the system. We start with a pair of coupled, first-order differential equations describing the behaviour of the two populations<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-Yr9y-TXG4jQ/T8QHeMFMJ7I/AAAAAAAAA1w/yxnj56W7irc/s1600/gen-coupled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-Yr9y-TXG4jQ/T8QHeMFMJ7I/AAAAAAAAA1w/yxnj56W7irc/s1600/gen-coupled.png" /></a></div>Now, these two populations are mutually exclusive subgroups of the total population - that is, total population z = x + y. So with a little <a href="http://mathbin.net/98300">mathematical trickery</a>, we can combine these two into a single equation, describing the behaviour of the total population<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-C1gcBg_PBxs/T8QH2KMy-uI/AAAAAAAAA2w/NnR7cF-WgvU/s1600/gen-pop-diff.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-C1gcBg_PBxs/T8QH2KMy-uI/AAAAAAAAA2w/NnR7cF-WgvU/s1600/gen-pop-diff.png" /></a></div>This is a second-order, linear differential equation, so has the general solution of the form<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-DJWkpsrxVPg/T8QH7X_tt3I/AAAAAAAAA24/EF1M9MK6dko/s1600/gen-pop.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-DJWkpsrxVPg/T8QH7X_tt3I/AAAAAAAAA24/EF1M9MK6dko/s1600/gen-pop.png" /></a></div>Where lambda values are given by<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-PaMwb8uKHiA/T8QHrhPcOZI/AAAAAAAAA2Y/3BOut9iA1Xk/s1600/gen-lambda.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-PaMwb8uKHiA/T8QHrhPcOZI/AAAAAAAAA2Y/3BOut9iA1Xk/s1600/gen-lambda.png" /></a></div>And A-coefficients are constants, which are determined by the system's initial conditions. If the initial total population is z0, and the inital size of the x population is x0, then we get<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-whAS0LUoOzM/T8QHk1JKDQI/AAAAAAAAA2I/sGOswwAFJiM/s1600/gen-avalues.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-whAS0LUoOzM/T8QHk1JKDQI/AAAAAAAAA2I/sGOswwAFJiM/s1600/gen-avalues.png" /></a></div>In other words, we now have an (analytical) equation which completely describes how the population will behave for any given set of variable.<br /><br />Incidentally, the equations for x and y have the same form as z. The only difference is the A-coefficients.<br /><br /><br /><b>Boom or Bust</b><br /><br />What we want to do is be able to determine the long term behaviour of the population based on the given set of variables - that is, will the population grow, or die out?<br /><br />For the coupled system above, there is only one stationary point, at (x,y) = (0,0) - i.e. when the total population is zero. What this means is, if the population is zero, it will stay zero. If it has any other value, then the population size will change over time.<br /><br />In terms of the stability of the system, if (0,0) is <i>stable</i>, the population will tend towards zero - i.e. will die out. If (0,0) is <i>unstable</i>, the population will tend to infinity - i.e. grow indefinitely.<br /><br />As it turns out, the lambda values above are the <a href="http://en.wikipedia.org/wiki/Eigenvalues">eigenvalues</a> of the coupled system, meaning their values determine the <a href="http://en.wikipedia.org/wiki/Stability_theory">stability</a> of the system:<br /><br />i) if both lambdas are <i>negative</i>, the system is stable and the population will <i>die out</i>;<br /><br />ii) if one or both of the lambdas are <i>positive</i>, the system is unstable and the population will <i>grow to infinity</i>.<br /><br />Now, if we take the limit of z(t) for t tends to infinity, we can simplify the population equation<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-keVvDl1dSAY/T8QH0q6OJsI/AAAAAAAAA2o/hWuMdqLcT7w/s1600/gen-lim-z.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-keVvDl1dSAY/T8QH0q6OJsI/AAAAAAAAA2o/hWuMdqLcT7w/s1600/gen-lim-z.png" /></a></div>What this tells us is, if lambda1 is less than one the population will die out. If lambda1 is greater than one, the population will grow.<br /><br />And we can use this to determine the conditions for instability - the conditions under which the population will grow. In this case the condition is <i>lambda1 > 0</i>. But this can be simplified down to<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-tDBZKTO0LK8/T8QHnmvSstI/AAAAAAAAA2Q/oCb44wNb8Co/s1600/gen-condition.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-tDBZKTO0LK8/T8QHnmvSstI/AAAAAAAAA2Q/oCb44wNb8Co/s1600/gen-condition.png" /></a></div>There's one last trick we can do - determine what proportion of the total population will be in the sub-population x (e.g. what what fraction of the population will be gay) as t tends to infinity.<br /><br />For this, we determine the equation for population x (in a similar fashion to z), and take advantage of the limits to get the equation<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-Hnmaj4NVd7Y/T8QHzjDYQ6I/AAAAAAAAA2g/6cZ1tplhx6E/s1600/gen-lim-xz.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-Hnmaj4NVd7Y/T8QHzjDYQ6I/AAAAAAAAA2g/6cZ1tplhx6E/s1600/gen-lim-xz.png" /></a></div>Note that this equation is time-independant, i.e. the proportion will tend to some constant value.<br /><br /><br /><b>Back to Basics</b><br /><br /><div class="separator" style="clear: both; text-align: left;">Just as a sort of check, we'll go back to the basic model. For this, we have </div><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-hI-Ol2UCd6w/T8QIGaF16bI/AAAAAAAAA3o/q4kS-kgQ0SA/s1600/simpled-coupled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-hI-Ol2UCd6w/T8QIGaF16bI/AAAAAAAAA3o/q4kS-kgQ0SA/s1600/simpled-coupled.png" /></a></div>Which gives lambda and A values<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-OnWLzfkQmrM/T8QICL8EgRI/AAAAAAAAA3I/p9VvybkRSrw/s1600/simple-lambda-a.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-OnWLzfkQmrM/T8QICL8EgRI/AAAAAAAAA3I/p9VvybkRSrw/s1600/simple-lambda-a.png" /></a></div>Which gives the population equation<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-M1W_mK4ADgQ/T8QIFd-3cMI/AAAAAAAAA3g/CGZWE3MSbsw/s1600/simple-pop.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-M1W_mK4ADgQ/T8QIFd-3cMI/AAAAAAAAA3g/CGZWE3MSbsw/s1600/simple-pop.png" /></a></div>Which behaves like the graphs at the top. And this tends to<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-6wGa6E2kUzo/T8QID1CV1cI/AAAAAAAAA3Y/_GnH6yridSo/s1600/simple-lim-z.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-6wGa6E2kUzo/T8QID1CV1cI/AAAAAAAAA3Y/_GnH6yridSo/s1600/simple-lim-z.png" /></a></div>And gives<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-GXXtk_blsNc/T8QIC8b4NsI/AAAAAAAAA3Q/l6jZyzPSQLQ/s1600/simple-lim-xz.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-GXXtk_blsNc/T8QIC8b4NsI/AAAAAAAAA3Q/l6jZyzPSQLQ/s1600/simple-lim-xz.png" /></a></div>That is, the proportion of the population that will be gay stabilises at the pre-defined 'birth' ratio - which is what we saw from the numerical model. Note also that this value is independent of initial conditions and doesn't vary over time.<br /><br /><br /><b>Simple Instability</b><br /><br />Finally, for the simple model, the condition for instability (growth) is given by<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-JpFZo6ZRwNo/T8QIA00SkrI/AAAAAAAAA3A/REe6x32pamM/s1600/simple-condition.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-JpFZo6ZRwNo/T8QIA00SkrI/AAAAAAAAA3A/REe6x32pamM/s1600/simple-condition.png" /></a></div>For the numerical values chosen above, this gives the condition <i>g < 0.606</i> - that is, if less than 61% of the population is 'born' gay, then the population won't die out. And 61% is a pretty high upper limit - a majority, in fact.<br /><br />Similarly, we can use this inequality to find under what conditions g=0.99 is viable. In this case, it requires a birth rate 100 times the death rate.<br /><br />So, if the death rate is 0.008 (i.e. 8 death for every 1,000 people), then it requires a birth rate of 0.8 (800 birth per 1,000 people). Now, for each 1,000 people, 99% is gay, meaning 10 straight people per 1,000. Of those, about half is female (5). In other words, each woman would have to give birth to <i>160</i> <i>babies per year</i>!<br /><br />Clearly this isn't feasible. But for comparisons sake, consider bees or ants - these species are <a href="http://en.wikipedia.org/wiki/Eusocial">eusocial</a>, meaning populations made of one fertile queen, and hundreds of (mostly) sterile drones. In this case, the high birth-to-death ratio is more reasonable, and the predominantly non-reproducing population is still viable.<br /><br /><br /><b>A More Complex Model</b><br /><br />In the simple model we assumed that only the straight population reproduces, and that both populations die at the same rate. So we make the following modifications:<br /><br />1) First of all, lets tweak the model so that the gay population has children as well, with some separate birth rate, <i>b1</i>.<br /><br />2) Of those born to gay couples, some proportion <i>g1</i> will be gay (NB/ <a href="http://en.wikipedia.org/wiki/Homosexuality#Etiology">studies</a> show that the majority of children raised by same-sex parents grow up to be heterosexual).<br /><br />3) And for completeness, lets say that the gay population has a different death rate as well, <i>d1</i>.<br /><br />Here's what the model looks like<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-vEEIrPp-cDU/T8QUxCGxMqI/AAAAAAAAA30/fJQkghYBHQo/s1600/model-flow2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-vEEIrPp-cDU/T8QUxCGxMqI/AAAAAAAAA30/fJQkghYBHQo/s1600/model-flow2.png" /></a></div>And here are the coupled equations<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-m58fdcPokGE/T8QHjQ79KtI/AAAAAAAAA2A/eTDcAV3Dquk/s1600/complex-coupled.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-m58fdcPokGE/T8QHjQ79KtI/AAAAAAAAA2A/eTDcAV3Dquk/s1600/complex-coupled.png" /></a></div>Now, all the previously derived general equations and such from earlier still apply. But in this case, the variables can't really be simplified, and writing them out in full would just be a mess.<br /><br />So if we take, for example, b and d as before, and <i>b1 = 0.00103</i>, <i>d1 = 0.01218</i>, and <i>g1 = 0.03</i>, here are the new phase diagrams for g = 0.07 and 0.99<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-mMUwf6Q-GH8/T8VAngBc3qI/AAAAAAAAA4Y/JCtVWYEpv8k/s1600/complex-phase-portraits.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="206" src="http://3.bp.blogspot.com/-mMUwf6Q-GH8/T8VAngBc3qI/AAAAAAAAA4Y/JCtVWYEpv8k/s400/complex-phase-portraits.png" width="400" /></a></div>For this model, the phase portraits are basically the same as for the simple model - i.e. the effect of using the modified model over the simple model is negligible.<br /><br /><br /><b>Complex Conditions</b><br /><br />So, for this model, the condition for growth looks like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-HBkVqdZDqHI/T8VSRZcKMTI/AAAAAAAAA5A/57U7XKWhJ5U/s1600/98729_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-HBkVqdZDqHI/T8VSRZcKMTI/AAAAAAAAA5A/57U7XKWhJ5U/s1600/98729_0.png" /></a></div>The important thing to note here is, if the condition for the simple model (<i>g<1-d/b</i>) is satified, then the condition for the complex model is necessarilty satisfied.<br /><br />In fact, if we set g to its maximum possible value 1 (i.e. all babies born to straight couple are gay), then the human race will still persist so long as <i>g1 > d1/b1</i>.<br /><br />And what this means is, so long as the birth rate (b1) is greater than the death rate (d1), then every baby born could be gay, the human race still wouldn't die out.<br /><br />However, given the low birth rate for gay couples (compared to death rate), this might not actually be satisfied IRL. In which case, an entirely gay population is still not sustainable.<br /><br />For the b and d values already chosen, we can use the condition to plot how the upper limit of g varies with g1<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-WS8hqLymnAc/T8VAoW8NguI/AAAAAAAAA4c/nCDR-UElQIA/s1600/g-g1-graph.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-WS8hqLymnAc/T8VAoW8NguI/AAAAAAAAA4c/nCDR-UElQIA/s1600/g-g1-graph.png" /></a></div>So for these values, the upper limit of g can increase by no more than 3.4%. So, again, the extra complexity doesn't give a significantly different result compared to the simple model.<br /><br />In fact, the additional variables only become significant when <i>(b1/d1)g1 > 0.15</i> (for the values above); or in general, when<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-eIQygV8O4_k/T8V0X5b3DeI/AAAAAAAAA5M/XmcT_FAbQQw/s1600/98755_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-eIQygV8O4_k/T8V0X5b3DeI/AAAAAAAAA5M/XmcT_FAbQQw/s1600/98755_0.png" /></a></div>So, if we keep b and d as before, <i>g=0.07</i>, and re-set b1=b, d1=d, and <i>g1=0.06</i>, then <i>(b1/d1)g1=0.152</i>, and the phase portrait looks like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-SgOJX-4FlPg/T8VNBHDB2II/AAAAAAAAA40/-Q_idDfuC_g/s1600/b1d1g1-0152.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="198" src="http://4.bp.blogspot.com/-SgOJX-4FlPg/T8VNBHDB2II/AAAAAAAAA40/-Q_idDfuC_g/s200/b1d1g1-0152.png" width="200" /></a></div>Here, the upper limit on g has risen to <i>0.67</i>. (cf: 0.61 for the simple model). And, if we set the initial condition <i>x0=0.07*z0</i>, then the limit of <i>x/z = 0.0697</i>; just less than g.<br /><br /><br /><br /><i><b>tl;dr - </b></i>So, would homosexuality ever mean the end of the human race? - Under some circumstances, yes. But in most (real world) cases, no. And even if a majority of the population were LGBT, the human race still wouldn't necessarily die out.<br /><br /><i>BUT</i>, as a final note - remember that these are very simplified models of population dynamics. So take the results with a grain of salt. The science of <a href="http://en.wikipedia.org/wiki/Human_sexuality">sexuality</a> is much more complex and much less abstract than this.<br /><br /><br />Oatzy.<br /><br /><br />[Putting my degree to good use.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-78218562586780683592012-03-24T20:09:00.001-07:002012-03-24T20:46:54.915-07:00Some Twitter InfographicsI did <a href="http://oatzy.blogspot.co.uk/2010/10/more-twitter-insights.html">some</a> <a href="http://oatzy.blogspot.co.uk/2010/10/follow-up-women-really-do-tweet-more.html">stuff</a> <a href="http://oatzy.blogspot.co.uk/2010/08/life-by-numbers.html">like</a> <a href="http://oatzy.blogspot.co.uk/2011/01/life-by-numbers-end-of-year-report.html">this</a> before. And I figured, while I was updating my <a href="http://oatzy.blogspot.co.uk/2012/03/friend-network-evolution.html">network graphs</a>, why not update some of the other graphics?<br /><br />And it helps that I worked out how to easily extract data from Twitter (see <a href="http://oatzy.blogspot.co.uk/2012/03/friend-network-evolution.html">previous blog</a>). The code is <a href="http://dl.dropbox.com/u/4635169/TwitterData/TwitterStuff.py">here</a>. Again, rate limits apply.<br /><br /><br /><b>Who Do I Follow?</b><br /><br />This is one of the ones I did <a href="http://oatzy.blogspot.co.uk/2010/10/more-twitter-insights.html">before</a> - collect together the bios of the people I follow, then make a word cloud (using <a href="http://www.wordle.net/">Wordle</a>)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-MY8giEB0tts/T25VjNJ2WKI/AAAAAAAAAyI/Wld_l3w4JF4/s1600/edscription-cloud.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="255" src="http://2.bp.blogspot.com/-MY8giEB0tts/T25VjNJ2WKI/AAAAAAAAAyI/Wld_l3w4JF4/s400/edscription-cloud.png" width="400" /></a></div>Basically, I follow a bunch of <i>geeks</i> and <i>writers</i>. Who like '<i>things</i>'. So really, same as a year and a half ago.<br /><br />I would point out though that 6 of the people I follow don't have bios, and about 7 just have lyrics.<br /><br />Data <a href="http://dl.dropbox.com/u/4635169/TwitterData/descriptions.txt">here</a>.<br /><br /><br /><b>Who Tweets the Most?</b><br /><br />These rates are worked out as <i>(total tweets posted)/(total days online)</i>. Obviously, the actually post rate will vary over different time scales..<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-qlkkDanYDpc/T25YCDm_HtI/AAAAAAAAAyQ/eC3mfEfBujQ/s1600/bubble-rates.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-qlkkDanYDpc/T25YCDm_HtI/AAAAAAAAAyQ/eC3mfEfBujQ/s1600/bubble-rates.png" /></a></div>Bubble chart (made with <a href="http://www-958.ibm.com/software/data/cognos/manyeyes/">ManyEyes</a>) - bubbles sized by tweet rate (the numbers on some of the bubbles).<br /><br />The graph below gives a better idea of relative rates, and 'rankings' (click to embiggen)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-vBMmhFRQmM8/T25ZNbW4D_I/AAAAAAAAAyY/GA8Kcn8mCDw/s1600/rates-graph.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="216" src="http://3.bp.blogspot.com/-vBMmhFRQmM8/T25ZNbW4D_I/AAAAAAAAAyY/GA8Kcn8mCDw/s400/rates-graph.png" width="400" /></a></div>The blue line is actual values.<br /><br />The orange is a logarithmic trend-line. It's a pretty good fit (<a href="http://en.wikipedia.org/wiki/R-squared">R2</a>=0.95); and, loosely speaking, it means ~70% of the tweets in my timeline come from ~30% of the people I follow. [cf: <a href="http://en.wikipedia.org/wiki/Pareto%27s_principle">Pareto Principle</a>]<br /><br />You get similar log-shaped graphs when you split up the genders. <br /><br />Full data <a href="http://dl.dropbox.com/u/4635169/TwitterData/twitterdata-2012317.txt">here</a>.<br /><br /><br /><b>Chattiest Gender?</b><br /><br />You can read all the explanation, caveats, etc. in the previous posts (<a href="http://oatzy.blogspot.co.uk/2010/10/more-twitter-insights.html">here</a> and <a href="http://oatzy.blogspot.co.uk/2010/10/follow-up-women-really-do-tweet-more.html">here</a>). I'm just going to go straight into the data.<br /><br />I follow 27 men and 21 women (excluding celebrities, etc.). The stats are as follow:<br /><blockquote class="tr_bq">Men:<br />Average = <i>6.21 tweets/day</i><br />Standard Deviation = <i>6.47</i><br /><br />Women:<br />Average = <i>13.79 tweets/day</i><br />Standard Deviation = <i>14.27</i></blockquote>For clarity, here's a <a href="http://en.wikipedia.org/wiki/Boxplot">boxplot</a> (made in <a href="http://www.r-project.org/">R</a>)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-0Q77gG1chig/T25nGij8GzI/AAAAAAAAAyg/hRcLc7Y42xw/s1600/gender-boxplot.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="168" src="http://1.bp.blogspot.com/-0Q77gG1chig/T25nGij8GzI/AAAAAAAAAyg/hRcLc7Y42xw/s400/gender-boxplot.png" width="400" /></a></div>Basically, the women tweet more on average, and their rates are more spread out than for the men. In fact, roughly three quarters of the men tweet less than half of the women. Also, there's one outlier in the female group.<br /><br />This is similar to what we found last time; although the women's average and spread aren't quite as high (average: 13.79 vs 19.21), and the men's average has increased slightly (6.21 vs 5.29).<br /><br />If you take the ratio of the averages, the women tweet 2.15 times as much as the men. But maybe I just follow particularly chatty women..<br /><br />Here's treemap (<a href="http://www-958.ibm.com/software/data/cognos/manyeyes/">ManyEyes</a>), which should give you a better idea of the gender balance (boxes sized by tweet rate)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-4TiedWMZN-4/T25wnQLHzYI/AAAAAAAAAyo/Ez436e3N_BY/s1600/gender-treemap.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="147" src="http://2.bp.blogspot.com/-4TiedWMZN-4/T25wnQLHzYI/AAAAAAAAAyo/Ez436e3N_BY/s400/gender-treemap.png" width="400" /></a></div>Specifically, the graphic above is 62.5% purple (female).<br /><br />Data <a href="http://dl.dropbox.com/u/4635169/TwitterData/twitterdata-2012317-gender.csv">here</a>.<br /><br /><br /><b>Where in the World Are My Followers?</b><br /><br />The site I used <a href="http://oatzy.blogspot.co.uk/2010/10/more-twitter-insights.html">last time</a> doesn't seem to exist anymore. So I'm using <a href="http://http//www.mapmyfollowers.com">MapMyFollowers</a> instead. As the name suggests, these are my followers, rather than just the people I follow. Nonetheless..<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-mL0g_dMpcmM/T26KPfkhfyI/AAAAAAAAAzg/81H-MWHZB14/s1600/followers-map.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="147" src="http://1.bp.blogspot.com/-mL0g_dMpcmM/T26KPfkhfyI/AAAAAAAAAzg/81H-MWHZB14/s320/followers-map.jpg" width="320" /></a></div>Mostly in the UK and the US. As you'd probably expect.<br /><br />I will point out though, some of the locations are a little suspect. Some people haven't made their location available so aren't included, and others seem to be in countries they couldn't possibly be in. But it's the best we can do.<br /><br />Here's a zoom in on the UK<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-PtN71lCkDPg/T26K6wlnh_I/AAAAAAAAAzo/EClnXtuQ8hg/s1600/followermap-uk.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="283" src="http://2.bp.blogspot.com/-PtN71lCkDPg/T26K6wlnh_I/AAAAAAAAAzo/EClnXtuQ8hg/s320/followermap-uk.jpg" width="320" /></a></div><br /><br /><b>What Do I Tweet?</b><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-CHVQ27fQmuo/T25xdK4cPGI/AAAAAAAAAyw/z87s7oy3wbA/s1600/tweet-cloud.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="163" src="http://1.bp.blogspot.com/-CHVQ27fQmuo/T25xdK4cPGI/AAAAAAAAAyw/z87s7oy3wbA/s400/tweet-cloud.png" width="400" /></a></div>Made with <a href="http://www.wordle.net/">Wordle</a>, with data from <a href="http://http//tweetstats.com/">TweetStats</a>. <br /><br />Words are sized by how often I tweet them; and by extension, @usernames are sized by how often I tweet those people.<br /><br />In fact, here are the people I 'mention' the most (TweetStats)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-TuJ8hyD7f8U/T250s0-5leI/AAAAAAAAAzA/sBQydHsUn_w/s1600/replies.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-TuJ8hyD7f8U/T250s0-5leI/AAAAAAAAAzA/sBQydHsUn_w/s1600/replies.png" /></a></div>Couldn't get a good source on who @replies me. That was one of the things Twoolr used to do..<br /><br /><br /><b>When Do I Tweet?</b><br /><br /><a href="http://twoolr.com/">Twoolr</a> used to be awesome for Twitter statistics. But sadly, when they left beta, they started charging. And their free service went to shit. Luckily, I found <a href="http://http//tweetstats.com/">TweetStats</a>. Weirdly, it doesn't need you to log-in or anything, but somehow it can pull data on (nearly) all your tweets - beyond the 3,200 limit. Strange.<br /><br />Here's some more graphs<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/--GfJ09HQ240/T251op7XOxI/AAAAAAAAAzI/pPosJDbES9E/s1600/tstat-agregates.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="132" src="http://2.bp.blogspot.com/--GfJ09HQ240/T251op7XOxI/AAAAAAAAAzI/pPosJDbES9E/s400/tstat-agregates.png" width="400" /></a></div>Basically, I tweet most on a Friday and Saturday, and at around 1-2pm.<br /><br />And I've never tweeted at 5am. But that's probably because I'm always asleep at 5am<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/--kbzY0qKUEU/T253uUBKPqI/AAAAAAAAAzQ/Ay3LvC8-408/s1600/sleep-dist-03-25-02-35-35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="http://3.bp.blogspot.com/--kbzY0qKUEU/T253uUBKPqI/AAAAAAAAAzQ/Ay3LvC8-408/s400/sleep-dist-03-25-02-35-35.png" width="400" /></a></div>Except that one time I got really drunk. (<a href="https://play.google.com/store/apps/details?id=com.lslk.sleepbot&hl=en">SleepBot</a>)<br /><br /><br /><b>How Much Do I Tweet?</b><br /><br />This is another one I used to go to Twoolr for. And, to be fair, I still could. But that only goes as far back as April '10, and its graphics aren't as clear. Here's <a href="http://http//tweetstats.com/">TweetStats</a> again<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-12mTM_qo3ww/T254W3ETZGI/AAAAAAAAAzY/CKOm4a700xw/s1600/tweetstat-usage.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-12mTM_qo3ww/T254W3ETZGI/AAAAAAAAAzY/CKOm4a700xw/s1600/tweetstat-usage.png" /></a></div>Like I said <a href="http://oatzy.blogspot.co.uk/2012/03/friend-network-evolution.html">before</a>, I didn't tweet much in my first year. In fact, I only posted 36 tweets in all of 2009.<br /><br />Now, the one problem with TweetStats is that 5 month gap in 2010. Why is this significant? Well, I was definitely tweeting during that time. In fact, by my estimates, over those 5 months I posted 5,724 tweets (~37tweets/day). So those 5 months account for 43% of all my tweets.<br /><br />See, the thing is, in 2010, I was out of university, single, and unemployed. I posted a total 8,823 tweets - 24tweets/day. Since I've been back at university, that number's dropped to 11tweets/day.<br /><br />That lull in Summer 2011 was when I was spending all my time on <a href="http://oatzy.blogspot.co.uk/2011/06/tumbling-part-one-some-background.html">Tumblr</a> and watching classic <a href="http://oatzy.tumblr.com/tagged/doctor-who">Doctor Who</a>. Incidentally, I haven't posted on Tumblr since the start of September '11. It's terribly addictive, you see. I wouldn't recommend it; unless you're addicted to Doctor Who and Sherlock, and have lots of time on your hands..<br /><br /><br />So yeah.<br /><br /><br />Oatzy.<br /><br /><br />[Self-indulgent statistics, and pretty illustrations.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-10863130595623522532012-03-14T17:53:00.000-07:002012-03-14T17:53:16.109-07:00Friend Network EvolutionBack in February 2009, I created <a href="https://twitter.com/#%21/Oatz">my Twitter account</a>, upon the insistence of my then-girlfriend. I didn't get it. Back then, Facebook was where it was at, I didn't really get Twitter's appeal. I was pretty much just following the handful of people I knew in real life, and Stephen Fry.<br /><br />So I didn't use it much. I'd pop up every now and then, post a couple tweets and give up on it again. At one point, I even developed an irrational dislike of it - whenever I saw a site had a "follow us on Twitter" button, it irked me for some reason.<br /><br />But at some point, towards the end of 2009/early 2010, I gave it yet another try. I don't know why. And even when I started using it, I was resistant; still half-heartedly hating it. But what was different this time, is I started chatting with people, and I was introduced to new people.<br /><br />People who don't get Twitter think it's just that thing where you can tell people when you're <a href="http://news.bbc.co.uk/1/hi/8204842.stm">eating a sandwich</a>. It's not. It's the people that make Twitter. (Tweens and arseholes notwithstanding.)<br /><br />But I'm going off on a tangent.<br /><br /><br />By August 2010, I was well into Twitter - I was posting around <a href="http://oatzy.blogspot.com/2010/08/life-by-numbers.html">30 tweets per day</a>, and I had around 30 friends*. And back then, I decided I wanted to see what my friends network looked like. So I broke out <a href="http://www.python.org/">Python</a> and the <a href="https://dev.twitter.com/docs">Twitter API</a>, I pulled data, and I made <a href="http://oatzy.blogspot.com/2010/07/follow-up-twitter-network.html">the graph</a>. Here's an updated version of it.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-m9gsbg4BUYQ/T2EuhsHRmEI/AAAAAAAAAxk/6EfE3F5jbGY/s1600/graph-20100831-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="203" src="http://1.bp.blogspot.com/-m9gsbg4BUYQ/T2EuhsHRmEI/AAAAAAAAAxk/6EfE3F5jbGY/s400/graph-20100831-alt.png" width="400" /></a></div>[click to embiggen] <br /><br />Fairly small, and tidy, and relatively uncomplicated. The bulk on the right is the people I knew in real life (from school, etc.) with a few strands of new acquaintances. Note how tightly packed and interconnected they are. To the left is mostly people I met through Twitter - and in particular, through PkmnTrainerJ.<br /><br />(In case you hadn't figured it out, the node and label sizes are proportional to number of connections.) <br /><br />By December 2010, I decided to have a look again.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-IsIJ823pD0c/T2EuvoRJ_mI/AAAAAAAAAxs/H6aoTk-HtWc/s1600/graph-20101208-alt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="222" src="http://3.bp.blogspot.com/-IsIJ823pD0c/T2EuvoRJ_mI/AAAAAAAAAxs/H6aoTk-HtWc/s400/graph-20101208-alt.png" width="400" /></a></div>Again, this is an update of the version I <a href="http://oatzy.blogspot.com/2010/11/follow-up-twitter-bffs.html">originally posted</a>; and in this case, I've tried to arrange it so that key people stay in approximately the same place.<br /><br />So you still vaguely have that left-right divide, but now there's much more mixing in the middle. I'd made some new friends, but more interesting is the people who were already in the graph who formed new connections with others in my graph.<br /><br />I'd also like to draw your attention to shinelikestars_ (formerly shinelikestars6) - take a look at the previous graph, can you spot him? From 2 shared connections to 8 in the space of two months. I don't think there's any sort of point I'm trying to make here. I'm just pointing it out 'cause it's interesting.<br /><br /><br />And for the next year and a half I didn't do any data collecting. It became too labourious - Twitter changed its API, so that my old code didn't work, and I had to do everything by hand.<br /><br />So, the latest graph was March 2011 (technical details below). As you can imagine, a lot can happen in a year and a half.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-WhWaY6DHaYg/T2EvUGqpX6I/AAAAAAAAAx0/gOCt4_v1Arw/s1600/graph-20120309-alt-clean.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="206" src="http://3.bp.blogspot.com/-WhWaY6DHaYg/T2EvUGqpX6I/AAAAAAAAAx0/gOCt4_v1Arw/s400/graph-20120309-alt-clean.png" width="400" /></a></div>First of all, the new people add, and the old people removed. But more importantly, look how much tighter, and how much more 'segmented' the graph is. <br /><br />There are now three major groups, loosely centred on the three most connected of my friends.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-KedBgwGLPlo/T2EvsH7UiTI/AAAAAAAAAx8/oZHHoHrj0Ws/s1600/graph-20120309-groups.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="166" src="http://4.bp.blogspot.com/-KedBgwGLPlo/T2EvsH7UiTI/AAAAAAAAAx8/oZHHoHrj0Ws/s320/graph-20120309-groups.png" width="320" /></a></div>On the far right are, again, the people I knew in real life. In particular, note how little that group has changed since the first graph.<br /><br />In the middle, we have 'Shiney's People' - people I was introduced to by shinelikestars_. And on the left are the people I was introduced to by PkmnTrainerJ.<br /><br />The smaller groups circled in red are <a href="http://en.wikipedia.org/wiki/Cliques">cliques</a> - smaller subgroups that, at least from my point of view, form their own little groupings, where (almost) everyone is interconnected. The bottom left 'clique', for example, is my parents and big sister.<br /><br />And I suspect, if you were to extend the graph beyond my network, you would find that those cliques are just parts of larger interconnected groups.<br /><br />In case you were wondering, PkmnTrainerJ and SallyBembridge are most connected, both with degree 14. shinelikestars_ is next most, with degree 10.<br /><br />Notable disappearing nodes - Benjidoom, who deleted his account, then created a new, private one (benjirino); and AimlessAmy, who is a long story.<br /><br />I should also point out that the people I follow who aren't friends with anyone else in my network do not appear in the pictured graphs. Not that they aren't as cool, they just don't join onto the graph.<br /><br />* I use friend here to mean people who I follow and who follow me back. Though I would probably consider all the people in my current network (including those not pictured) friends to some degree.<br /><br /><br /><b>Technical stuff</b><br /><br />You can read details on how I collected the data before, in the <a href="http://oatzy.blogspot.com/2010/07/twitter-friend-network.html">previous blogs</a>. But, as I say, those methods don't work anymore.<br /><br />For this run, I read up on the API, and found some bits that don't need authentication to grab and manipulate.<br /><br />First, you can grab a list of a user's friends with this URL<br /><br /><i>https://api.twitter.com/1/friends/ids.xml?screen_name=<username></i><br /><br />This will give you a list of the friends ID numbers, so you also need to use this to grab usernames<br /><br /><i>https://api.twitter.com/1/users/lookup.xml?user_id=<idnumber></i><br /><br />There is also a URL to check if a user follows another user<br /><br /><i>https://api.twitter.com/1/friendships/exists.xml?user_id_a=<idnumber1>&user_id_b=<idnumber2></i><br /><br />Which works through the browser, but I couldn't get to work in my code. So in place, I used the site <a href="http://doesfollow.com/">DoesFollow.com</a>; partly because it uses the URL scheme <i> </i><br /><br /><i>DoesFollow.com/user1/user2</i>.<br /><br />Which is very convenient. Though I do worry all the requests might be putting strain on that site's server.<br /><br />So, putting all that together with a bit of Python, you get <a href="http://dl.dropbox.com/u/4635169/FriendLinks.py">something like this</a>.<br /><br /><br />A few important points:<br /><br />1) It will take a while to run. I have ~50 friends, and it took well over an hour to pull all the data. In terms of <a href="http://en.wikipedia.org/wiki/Computational_complexity_theory">computational complexity</a>, it's O(n^2), but each of those operations takes a significant amount of time.<br /><br />2) Twitter has an <a href="https://dev.twitter.com/docs/rate-limiting">API limit</a> of 150 requests per hour. The number of API requests the code will make is ~ the number of friends being looked up. I think. Which means, if you have more than 100 or so friends, this code probably won't work. Sorry. There might be a way around it, but I don't know how.<br /><br />3) Obviously, this doesn't work on protected accounts. So for those people you will have to grab data by hand. Though it's not <i>too</i> bad for a small enough number of people.<br /><br />If you do want to use the code, I've made it so you just have to change the username at the top, and run it. You will need to install Python though.<br /><br />For creating the graphs, I previously use <a href="http://www-958.ibm.com/software/data/cognos/manyeyes/">ManyEyes</a>. But I moved to using <a href="http://gephi.org/">Gephi</a>, because it allows for more customising. The output from the code is a text file with a list of name pairs, which you can import directly into Gephi. It will build the graph for you, and then you're free to play as you like.<br /><br /><br />Aaand... Yeah, I think that's about it.<br /><br /><br />Oatzy.<br /><br /><br />[shinelikestars6 lost his <a href="http://oatzy.blogspot.com/2010/07/follow-up-twitter-network.html">red circle</a>, on account of he isn't my nemesis anymore.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-12883192651439819172012-03-05T15:52:00.000-08:002012-03-05T15:56:22.728-08:00So What Was the Best Day To Go Shopping?Alright, let's be done with this.<br /><br />Just a quick reminder - what I did was collect Foursquare check-in data for various shopping centres around the UK, in the hope that the data might show something interesting.<br /><br />Previous blog posts on this data collecting - <a href="http://oatzy.blogspot.com/2011/12/best-day-to-go-shopping.html">Best Day to Go Shopping</a>, <a href="http://oatzy.blogspot.com/2011/12/panic-saturday.html">Panic Saturday</a>, <a href="http://oatzy.blogspot.com/2011/12/christmas-eve.html">Christmas Eve</a>. <br /><br />Anyway, I've been collecting data for over 3 months now. And that seems like quite enough.<br /><br />Here's a graph of (normalised) averaged check-ins on each day of the week for 4 periods:<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-Adh7n-0kQsg/T1VKeainmOI/AAAAAAAAAxU/OBuvXmtq-hw/s1600/checkin-averages.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-Adh7n-0kQsg/T1VKeainmOI/AAAAAAAAAxU/OBuvXmtq-hw/s1600/checkin-averages.png" /></a></div>DecAv (blue) is 21st Nov 2011 to 18th Dec 2011<br />ChrAv (grey) is 19th Dec 2011 to 1st Jan 2012<br />JanAv (orange) is 5th Jan 2012 to 2nd Feb 2012<br />FebAv (green) is 6th Feb 2012 to 4th Mar 2012<br /><br />Aside from the two weeks either side of Christmas (grey) - when people, apparently, did their shopping more midweek - the pattern is basically the same.<br /><br />For further clarity, here's the average of those three averages (excluding Christmas)<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-fGxTumzfPAY/T1VMdG8f57I/AAAAAAAAAxc/a2jJF59ijh0/s1600/checkin-av.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-fGxTumzfPAY/T1VMdG8f57I/AAAAAAAAAxc/a2jJF59ijh0/s1600/checkin-av.png" /></a></div>And here is the order of days, from least to most busy ('relative busyness' in brackets):<br /><br />1) Wednesday (1.00)<br />2) Monday (1.01)<br />3) Tuesday (1.03)<br />4) Thursday (1.11)<br />5) Sunday (1.15)<br />6) Friday (1.24)<br />7) Saturday (1.78)<br /><br />Note that the differences between Monday, Tuesday, and Wednesday are not statistically significant - they're essentially the same, and are likely to be as busy as each other/not noticeably different.<br /><br />So, to answer the title question - <i>Monday</i>, <i>Tuesday</i>, and <i>Wednesday</i> are the best days to go shopping. At least, in as much as they're the days shopping centres are likely to be least busy. And, as you'd expect, Saturday is, by far, the worst/most busy.<br /><br />And the last thing to point out is that these are the averages over 20 shopping centres for a ~3 month period - numbers for specific locations, and at different times (eg holidays) are likely to deviate from the averages.<br /><br />And, basically, that's that.<br /><br />If you're interested, you can see the raw check-in data <a href="http://dl.dropbox.com/u/4635169/checkins.txt">here</a>. <br /><br /><br />Oatzy.<br /><br /><br />[That was definitely worth the effort.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-74228774346072159072012-03-03T16:12:00.000-08:002012-03-03T16:12:33.357-08:00Where are the Carriages?<b><rant> </b><br /><br />So it's around 9am, and you're waiting for a train. The trains in your area are a bit scummy, but whatever, you can't drive, and you've got to get to work/uni somehow. It's pretty busy, what with it being 9am, and when the train finally pulls up.. it's a single carriage.<br /><br />Now a single car has <a href="http://en.wikipedia.org/wiki/British_Rail_Class_142">around 50 seats</a>, and this train is packed tight, with people standing in the aisles and the doorways, being forced to get intimate. And with everyone on board the conductor can barely get through the door. There are clearly enough people on this train to fill two cars, with people still having to stand.<br /><br />So what gives? You're pretty pissed about having to stand for half an hour, and you've been inadvertently touched by strangers in ways you're not comfortable with. So you get your complaining hat on, and you turn to the internet to unleash the fury.<br /><br />The train company's website directs you to their <a href="https://twitter.com/#%21/northernrailorg">Twitter feed</a>, where a poor public relations person is taking a barrage of vitriol from other angry commuters, while doing their best to remain polite and professional. <br /><br />Several other people have already made your complaint, but all they're getting in return is "<a href="https://twitter.com/#%21/northernrailorg/status/175648061274591232">Apologies we try to avoid this where possible</a>", or that the short trains were "<a href="https://twitter.com/#%21/northernrailorg/status/174544893220360193">due to operational reasons</a>". And that's not really a satisfying response. You're not even sure it means anything.<br /><br /></rant><br /><br /><br /><b>So Where Are The Carriages?</b><br /><br /><i>Disclaimer</i>: I have no idea how the train company actually operates. I just like to write blogs about applied maths.<br /><br />Here's the setup - you're in charge of logistics for the train company. You have a fixed number of trains/cars, and, for each day, a list of services and expected numbers of passengers.<br /><br />How do you apportion the carriages between the services?<br /><br />Obviously, every service needs at least one car, and services with more passengers need more cars.<br /><br />So if you had, for example, three services with 50, 100, and 150 passengers respectively, and 6 cars, then it's easy to divvy them up - one for the first, two for the second, and three for the third.<br /><br />But in general, it won't be possible to divide up the cars exactly like that; so what do you do about remainders?<br /><br />This is actually similar to the problem of apportioning parliamentary seats between states in the US.<br /><br />Currently there are 435 seats in the <a href="http://en.wikipedia.org/wiki/United_States_House_of_Representatives#Apportionment">House of Representatives</a>, which need to be shared out between the 50 states according to each state's population - the idea being that each seat should represent roughly the same number of voters. <br /><br />There are <a href="http://www.ctl.ua.edu/math103/apportionment/quick.htm">various methods</a> for doing this, but I'm only going to go over two of them.<br /><br /><br /><b>The Hamilton method </b><br /><br /><i><a href="http://en.wikipedia.org/wiki/Hamilton_method">Hamilton's Method</a></i> is the easiest and most intuitive.<br /><br />Say you've got only two services - service A has 85 passengers, service B has 115 passengers (200 passengers total). And you happen to have 4 carriages - a total 200 seats. So seats for everyone! Except service A needs 1.7 cars, and service B needs 2.3 cars. And you can't divide a car into two chunks.<br /><br />But for starters, you can give one car to service A (50 seats), and two to service B (100 seats). So what about the fourth? Regardless of which service you give it to, some people are going to end up having to stand. So what you want to do is minimise that number.<br /><br />For service A, 35 people need a seat. For service B, 15 people need a seat. So it makes sense to give the last car to service A. 15 people still have to stand, but - short of building more cars - there's really nothing you can do about that.<br /><br />This is also known as the <a href="http://en.wikipedia.org/wiki/Largest_remainder_method"><i>largest remainder method</i></a>.<br /><br />What if the passenger numbers were 75 and 125? Well in that case, I guess it would have to be a judgement call.<br /><br />Anyway, that's the basic idea of the Hamilton method - divide up the cars as far as you can, then give the remaining car(s) to the service(s) which 'need them most'.<br /><br />Things get trickier when you're dealing with larger numbers of passengers and cars, but ultimately it's pretty straight-forward.<br /><br /><br /><b>The Huntington-Hill Method</b><br /><br />Where trains differ from apportioning of parliamentary seats, is that a carriage has a fixed capacity, whereas the number of people a seat can represent is free to change.<br /><br />And this means that you won't encounter some of the '<a href="http://en.wikipedia.org/wiki/Alabama_paradox">quirks</a>' that arise from the Hamilton method - such as the <i>Alabama Paradox</i> (where increasing the total number of seats can mean a state losing seats), or the <i>Population Paradox</i> (where increasing a state's population can result in that state losing seats).<br /><br />[You won't encounter them, but they're worth mentioning 'cause they're pretty cool.] <br /><br /><a href="http://en.wikipedia.org/wiki/Huntington%E2%80%93Hill_method"><i>Huntington-Hill's Method</i></a> - the one currently used by the House of Representatives - is, generally, a more powerful method than Hamilton's, and <a href="http://www.cut-the-knot.org/ctk/Democracy.shtml">isn't susceptible</a> to the various paradoxes. Another benefit is that it can be set up to guarantee that each state will get at least one seat.<br /><br />It works by assigning seats, one by one, based on each state's '<a href="http://en.wikipedia.org/wiki/Highest_averages_method">priority quotient</a>' - itself based on the state's population, and a 'modified divisor' based on the number of seats already allocated to that state.<br /><br />But one advantage Hamilton's Method has over HHM, is that it will always give each service its ideal number of cars, either rounded up of down to the nearest whole number. So if a service's ideal number is 3.67, then Hamilton's method will assign either 3 or 4 cars to that service.<br /><br />HHM, on the other hand, can 'violate quota' - that is, it can result in a given service being assigned more or fewer cars than it's ideal number (e.g. the 3.67 service might end up with only 2 cars). But this problem only occurs when the number of cars is fixed prior to apportioning. And sadly, it usually is.<br /><br /><br />As an aside, it turns out it's <a href="http://en.wikipedia.org/wiki/Alabama_paradox#Impossibility_result">impossible</a> to find a 'perfect' method of apportioning - one with neither paradoxes nor quota violating. Incidentally, the <a href="http://en.wikipedia.org/wiki/Category:Voting_theory">mathematical theory</a> surrounding voting is <a href="http://www.newscientist.com/article/mg20627581.400-electoral-dysfunction-why-democracy-is-always-unfair.html">quite fascinating</a>. You know, if you're into that sort of thing.<br /><br /><br /><b>Unfortunately..</b><br /><br />Either method would work perfectly well. The problem is, while rounding up or down to the nearest car may seem trivial, the 50 seats that that one car represents can be a significant gain/loss for a service.<br /><br />The lack of precise information can cause problems as well. You can only estimate how many passengers a given service will have in advance, and if you under-estimate, people are gonna be pretty pissed.<br /><br />And on top of that, the numbers have to be recalculated from time to time as passenger numbers change..<br /><br />Basically, getting the number of cars right can be tricky.<br /><br /><br />Of course, this all assumes that Northern Rail (or whatever company) has enough carriages to adequately satisfy its needs in the first place. Some how I doubt that's the case.<br /><br />So ideally, NR needs to work out how many cars it actually needs, and build them. But I can't see that happening. And even if it did happen, it'd probably mean higher fares. And fares are pretty bad as they are.<br /><br />Really, though, NR could do with a complete makeover just in general, given <a href="http://en.wikipedia.org/wiki/Northern_Rail#Criticism">how</a> <a href="https://twitter.com/#%21/northernfail">shitty</a> it is. Seriously, you should see the difference between them and the London Midland service. Ridiculous.<br /><br />Incidentally, I also have strong feelings about the buses; don't even get me started. Trams are alright though - one every 10mins, seldom short of seats, sufficient leg-room.. bliss.<br /><br /><br /><i><b>tl;dr</b></i> - Where are the carriages, then? As it turns out, it's probably just that they <a href="https://twitter.com/#%21/northernrailorg/status/175558593863622656">don't have enough</a> to go around.<br /><br />This is what we get for <a href="http://en.wikipedia.org/wiki/Privatization_of_British_Rail">privatising</a> the trains... <br /><br /><br />Oatzy.<br /><br /><br />[My complaining hat is a trilby. I like to look bad-ass while I'm complaining.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-4808830782882602832012-02-24T18:33:00.000-08:002012-02-24T18:43:49.546-08:00A Problem With PuzzlesHere's a puzzle, tweeted by <a href="https://twitter.com/#%21/Aetherling/status/170531402578796545">Aetherling</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-htfd7qLN9pQ/T0g2wVEFsOI/AAAAAAAAAw4/zGJpZ6Aw59A/s1600/puzzle.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-htfd7qLN9pQ/T0g2wVEFsOI/AAAAAAAAAw4/zGJpZ6Aw59A/s1600/puzzle.png" /></a></div>Have a go at it yourself. Solution below.<br /><br />You see these sorts of puzzle every now and again. And there are usually common 'tricks' to them.<br /><br />My first instinct was to add the digits. Nothing. Modulus? Nope. Sum and modulus, multiply digits, square, divide? Nope.<br /><br />So, what?<br /><br />For some reason, I decided I was going to try some algebra - ignore the fact that the left-hand sides of the equations are numbers, and treat them as variables with some unknown values. And then, add up whatever those values are to get the numbers on the right of the equations.<br /><br />i.e. 9313 -> '9' + 2x'3' + '1' = 1; 1111 -> 4x'1' = 0; etc.<br /><br />So from 1111=2222=3333=5555=7777=0 we get that <i>1,2,3,5,7 = 0</i><br /><br />From 0000=6666=9999=4 we get that <i>0,6,9 = 1</i><br /><br />And from 6855=3, 1+'8'=3 => <i>8=2</i><br /><br />4 is undefined. And that makes the answer to the puzzle <b>2581=2</b>.<br /><br />It took me about half an hour to come up with that answer.. <br /><br /><br /><b>"If all you have is a hammer, everything looks like a nail"</b><br /><br />That is the right answer. But the puzzle suggests that a preschooler would get the correct answer with ease. And I doubt a preschooler would do so with algebra.<br /><br />So, what would a preschooler do? And why does it take 'higher educated' people so much longer?<br /><br />What's the 'correct' answer? Take a look at the below <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-Sf9UQw3_K5g/T0g28vCT_XI/AAAAAAAAAxA/NF4y3o8_Bq4/s1600/puzzle-soln.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-Sf9UQw3_K5g/T0g28vCT_XI/AAAAAAAAAxA/NF4y3o8_Bq4/s1600/puzzle-soln.png" /></a></div><br />Basically, it's <b><i>the number of loops</i></b> in the digits on the left-hand side. This had to be pointed out to me by <a href="https://twitter.com/#%21/Aerliss/status/170574146814738433">Aerliss</a>.<br /><br />But here's the interesting thing - take a look at the values I assigned to the number.<br /><br />I got to the same answer, and effectively 'derived' the number of loops in the digits, without realising that that's what I was doing.<br /><br /><br />Source on the subheading quote - <a href="http://en.wikipedia.org/wiki/Law_of_the_instrument">Maslow's Hammer</a>.<br /><br />It's not a perfect analogy, but the idea is this - the more mathematical training you have, the more techniques you'll be trying to use to solve the problem; possibly causing you to miss the 'obvious' answer. As, indeed, I did.<br /><br />In this analogy, maths is the hammer.<br /><br />And, strictly speaking, this puzzle isn't a maths problem - even though it's presented as if it were. And even though it <i>can</i> be solved using maths.<br /><br /><br /><b>Another Problem</b><br /><br />Here's another puzzle, recently posted on <a href="http://www.reddit.com/r/math/comments/p81fp/this_was_brought_to_my_attention_on_a_bar_napkin/">Reddit</a><br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-CqzdD2_HsnY/T0g5WACVuLI/AAAAAAAAAxI/qjRm2qVzaTs/s1600/reddit-puzzle.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="247" src="http://4.bp.blogspot.com/-CqzdD2_HsnY/T0g5WACVuLI/AAAAAAAAAxI/qjRm2qVzaTs/s320/reddit-puzzle.png" width="320" /></a></div>The problem here is that the correct answer isn't known.<br /><br />The 'obvious' answer (in as much as the one that most commenters came up with) was to add the four cross numbers, divide by 10, and round to the nearest whole number. In that case, the missing number is (23+20+12+3)/10 = 5.8 ~ <i>6</i> (B).<br /><br />But that doesn't explain the set up - why the crosses? Are the positions of the numbers in the cross significant? And rounding is a little messy.<br /><br />The most compelling proposed solution, once again, doesn't actually involve any maths:<br /><blockquote class="tr_bq"><b>"</b><i>As far as I can tell, the answer is C: 7 [...] The formula for the number in the center is the number of unique letters shared by the top and bottom numbers as written in English plus the number of unique letters shared by the right and left numbers..<b>"</b></i></blockquote><br /><b>Finite Rule Paradox</b><br /><br />At any rate, what the <a href="http://www.reddit.com/r/math/comments/p81fp/this_was_brought_to_my_attention_on_a_bar_napkin/">comment thread</a> shows is, in the absence of a 'true' answer, any answer is potentially correct with some valid justification. Case in point, I've just presented two different possible answers with two valid justifications.<br /><br />This is an example of <a href="http://en.wikipedia.org/wiki/Wittgenstein_on_Rules_and_Private_Language">Wittgenstein's Finite Rule Paradox</a>.<br /><br />Stated explicitly - "<i>This was our paradox: no course of action could be determined by a rule, because any course of action can be made out to accord with the rule</i>"<br /><br />Basically, what I described above..<br /><br />There's a clearer explanation <a href="http://mathoverflow.net/questions/4442/is-there-a-theorem-that-says-that-there-is-always-more-than-one-way-to-continue/75260#75260">here</a>. The topic is also a plot point in the <a href="http://www.amazon.co.uk/Oxford-Murders-Guillermo-Martinez/dp/0349117233">novel</a> (and subsequent <a href="http://www.imdb.com/title/tt0488604/">film</a> adaptation) <a href="http://en.wikipedia.org/wiki/The_Oxford_Murders_%28novel%29">The Oxford Murders</a>; recommended if you're into maths-fueled murder mysteries.<br /><br />The paradox is more apparent in '<i>find the next term in the sequence</i>' puzzles.<br /><br />For example, if you're given 2, 4, 8, 16 and asked what the next number is, then the obvious answer is 32 - twice the previous number/powers of two.<br /><br />However, 31 is an equally valid continuation, as Marcus du Sautoy <a href="http://www.guardian.co.uk/books/2005/feb/05/featuresreviews.guardianreview13">explains</a>:<br /><blockquote class="tr_bq"><b>"</b><i>[L]et me explain why 31 can be a perfectly legitimate way to continue the sequence 2, 4, 8, 16 ... Draw three dots on a circle and join the dots with lines. The circle gets divided into four pieces. If you now take four dots on the circle and draw all the lines between the dots then you cut the circle into eight pieces. Five dots leads to 16 pieces. But if you draw all the lines between six dots you will only get 31 pieces rather than the 32 you'd expect.<b>"</b></i></blockquote><br />In fact, you can define a function, f(n), which satisfies the first 4 terms of the above sequence, and will give <i>any</i> number you like for the fifth.<br /><br /><br /><b>The Easy Path</b><br /><br />How is that possible? <a href="http://en.wikipedia.org/wiki/Lagrange_interpolation">Lagrange Interpolation</a>.<br /><br />[Discussed in <a href="http://www.amazon.co.uk/Professor-Stewarts-Hoard-Mathematical-Treasures/dp/1846682924">Professor Stewart's Hoard of Mathematical Treasures</a>]<br /><br />Lagrange interpolation applies to any (numerical) sequence of any (finite) length; so, there are infinitely many ways to continue any given sequence - and, necessarily, one cannot say with absolute certainty that a given sequence will continue in one particular way, only.<br /><br />Unless the person who set the puzzle says otherwise. Because if it's their puzzle, they know what answer they're looking for. And if you try to score easy marks on an exam by citing Wittgenstein, you're probably going to fail.<br /><br />Of course, Lagrange polynomials - although interesting that they exist - are likely to be the least interesting way to continue a sequence. Certainly, the equivalent Lagrange polynomial isn't as meaningful or interesting as the circle division sequence; even though they both give the same first 5 terms.<br /><br />And a Lagrange polynomial will probably never be the most obvious/aesthetically pleasing solution.<br /><blockquote class="tr_bq"><b>"</b><i>[H]e conjectured that, though in principle all answers were equally probable, there might be something engraved on the human psyche [...] which guided most people to the same place, to the answer that seemed the simplest, clearest or most satisfying. He was definitely thinking that some kind of aesthetic principle was operating a priori which only let through a few possible answers for the final choice.<b>" - The Oxford Murders, Chptr 9</b></i></blockquote>Though, as seen in the Reddit puzzle, the simplest answer isn't necessarily the most correct one.<br /><br /><br /><b>There Must Be Another Way</b><br /><br />The important thing is, the existence of Lagrange polynomials 'proves' the Finite Rule Paradox for numerical sequences.<br /><br />By the same logic, there must exist some functions, f(a,b,c,d), which satisfy the first 20 equations in the first puzzle, but give some values other than 2 for the last. Or, multiple functions which do give 2, but by different methods to the correct one.<br /><br />Of course, such functions aren't likely to be easy to find. Especially when there are 20 'initial rules' to satisfy. But the fact that there are finitely many rules means it's possible.<br /><br />Not that counting loops, or manipulating letters count as mathematical functions. But the point still stands.<br /><br /><br />At any rate, the important thing to remember is not to get too obsessed with the consequences of the Finite Rule Paradox. Or you may end up trying to lobotomise yourself to prove a theory.<br /><br /><br />Oatzy.<br /><br /><br />[<i>What comes next</i> - <i>3, 3, 5, 4, 4, 3, 5, 5, 4, ?</i>]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com3tag:blogger.com,1999:blog-14769935.post-40561091467786034302012-02-18T12:22:00.000-08:002012-02-18T12:22:16.970-08:00Funny StorySo there's four of us at the pub, and we're playing the one word game. And we decide that, since this is the future, we should play a marathon game, via Twitter (complete with hashtag, #<a href="http://twitter.com/#!/search/realtime/OneWordGame">OneWordGame</a>).<br /><br />The One Word Game is where a group of people attempt to collectively tell a story, one word at a time. (Also known as <a href="http://improvencyclopedia.org/games/Word_at_a_Time_Story.html">Word at a Time Story</a>).<br /><br />At this point, we've been playing for about two weeks.<br /><br /><br />So what's our story? Before that, lets play a game.<br /><br />Below are two 'stories' - one of them is our word-at-a-time story, the other has been randomly generated (using <a href="http://en.wikipedia.org/wiki/Markov_chain#Markov_text_generators">Markov text generation</a>). <br /><br />Which is which?<br /><br /><blockquote class="tr_bq"><em>When the wizard was masturbating upon a picture of your face which is ugly he said that a giant octopus with swashbuckling offspring however walrus munched on his wardrobe handles while reciting to Steve the only one who played the harmonica case under his umpalumpa cheese tasted spectacular when dipped in French wine and peanut butter making apples with a slightly tangy bumhole that had smelly teeth and pickled onions with slices of cherry on toast. Suddenly a massive caterpillar spawned from a seed which smells of turnip. When select acorns they exploded penises all turned into a</em></blockquote><br /><blockquote class="tr_bq"><em>There was no further sound other than the soft tinkle of water. But in the middle of the surface area were encrusted with hard-caked earth but the girl who was confined them to perpetual darkness. This time there would be a very narrow neck extending about an inch above the rest. Could be worth a try I think he changes his name from time to time. Since it was meant to support an entire career However I</em></blockquote><br />The correct answer is, ours is the first one - probably evident from the vulgar language.<br /><br />As for the second one; I've discussed Markov generators <a href="http://oatzy.blogspot.com/2011/04/markovs-next-tweet.html">before</a>. In the simplest case, they work by randomly picking words based on how likely they are to follow the preceding word. This particular example was generated from the first four chapters of <a href="http://en.wikipedia.org/wiki/Dirk_Gently%27s_Holistic_Detective_Agency">Dirk Gently's Holistic Detective Agency</a> (Douglas Adams) using <a href="http://www.beetleinabox.com/mkv_input.html">this site</a>.<br /><br />How does this relate to our game?<br /><br />Well, the game was spread out over such a period of time that, usually, I'd forgotten the story much beyond the previous few words. So, I was picking my words based on what seemed like a coherent continuation to those few. Not unlike a Markov generator.<br /><br />And, you have to admit, the two make about as much sense as each other.<br /><br /><br />At any rate, the game is still on-going..<br /><br /><br />Oatzy.<br /><br /><br />[What? I never claim our story was funny..]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-86981490308937684362012-01-28T14:07:00.000-08:002012-01-28T14:07:17.752-08:00Simple Harmonic SleepSo I started using <a href="https://market.android.com/details?id=com.lslk.sleepbot&hl=en">SleepBot Tracker</a> to track my sleep. This is what it's looking like so far<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-gSpj5gDecfM/TyRlg80a0fI/AAAAAAAAAvs/322drrDvZEQ/s1600/screenshot-crop.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-gSpj5gDecfM/TyRlg80a0fI/AAAAAAAAAvs/322drrDvZEQ/s1600/screenshot-crop.png" /></a></div>Basically, I'm averaging about 8 hours, except on those two days near the start where I had to get up early for exams.<br /><br />Now, being a physics student, the graph reminded me of that for damped simple harmonic motion (starting from 20/01, ignoring the first 3 points). And being a crazy person, I decided to try and model my sleep as such.<br /><br />So what's <a href="http://en.wikipedia.org/wiki/Simple_harmonic_motion">simple harmonic motion</a>?<br /><br /><i>Simple harmonic motion is a type of periodic motion with a restoring force directly proportional to the </i><i>the system's</i><i> displacement</i> <i>from equilibrium</i>.<br /><br />For example, a pendulum is a simple harmonic oscillator - it has periodic motion, its equilibrium is the lowest point of the swing, and the restoring force is gravity.<br /><br />Now, one <i>could</i> argue that sleep is SHM-like - we have some typical sleep length (equilibrium), and if we get too little sleep, then we'll tend to sleep more in response, and vice versa (restoring force). But it's a dubious analogy at best.<br /><br />A <a href="http://en.wikipedia.org/wiki/Damping">damped</a> harmonic oscillator is one with damping, which <i>tends to reduce the amplitude of oscillations</i>. So, like air resistance in the case of the pendulum, which eventually causes it to stop swinging.<br /><br />I'm not sure what the sleep-based analogy for damping would be.<br /><br /><br />There's a standard equation for defining a (weakly) damped harmonic oscillator. It looks like this:<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-ZGAulaMs6TI/TyRnbs7tPtI/AAAAAAAAAv0/UdT-jzBaKQY/s1600/78730_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-ZGAulaMs6TI/TyRnbs7tPtI/AAAAAAAAAv0/UdT-jzBaKQY/s1600/78730_0.png" /></a></div>Where:- <i>A0</i> is the initial displacement, the <i>e</i> bit is the decaying term, <i>gamma</i> is the damping coefficient (which determines how quickly the oscillations decay), <i>cos()</i> is the oscillating term, <i>omega</i> is the (damped) frequency of oscillation, <i>t</i> is time (in days), <i>phi</i> is the phase shift, and <i>C</i> is the equilibrium amplitude.<br /><br />So working out the variables from the data, the model equation for my sleep looks something like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-6TXKTQpiJNs/TyRpWhWz9UI/AAAAAAAAAwU/DJdawZGoEXw/s1600/79122_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-6TXKTQpiJNs/TyRpWhWz9UI/AAAAAAAAAwU/DJdawZGoEXw/s1600/79122_0.png" /></a></div>And the graph looks like this<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-BG2Xsatzd5U/TyRoJS_StUI/AAAAAAAAAwE/BNyR3CjU03Y/s1600/wolframalpha-graph1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-BG2Xsatzd5U/TyRoJS_StUI/AAAAAAAAAwE/BNyR3CjU03Y/s1600/wolframalpha-graph1.png" /></a></div>And to prove I'm not entirely crazy, here are the real values and the model values plotted together<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-yiSuWiC6V_M/TyRoZdvEIOI/AAAAAAAAAwM/944_83vDwOU/s1600/data-model1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="214" src="http://3.bp.blogspot.com/-yiSuWiC6V_M/TyRoZdvEIOI/AAAAAAAAAwM/944_83vDwOU/s400/data-model1.png" width="400" /></a></div>Not a bad fit, right? [<a href="http://en.wikipedia.org/wiki/Mean_square_error">error</a> 0.19]<br /><br />In fact, you might notice the real data is still oscillating a little. But the equation outlined above tends to a constant amplitude of 8.3 (no more oscillations).<br /><br />To account for this, we could add a baseline oscillation term <br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-N1jUmHxqfO4/TyRq2Xr6nYI/AAAAAAAAAwc/1dyqYbvhKLo/s1600/79124_0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-N1jUmHxqfO4/TyRq2Xr6nYI/AAAAAAAAAwc/1dyqYbvhKLo/s1600/79124_0.png" /></a></div>And fitting the data again, here's what the graph looks like<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/--ypD8_F2Tjw/TyRrC0jR26I/AAAAAAAAAwk/ei1n1ro9ais/s1600/data-model2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="195" src="http://4.bp.blogspot.com/--ypD8_F2Tjw/TyRrC0jR26I/AAAAAAAAAwk/ei1n1ro9ais/s400/data-model2.png" width="400" /></a></div>Arguably, a <i>slightly</i> better fit. [error 0.16]<br /><br />So yeah. Basically, I'm procrastinating..<br /><br /><br />Oatzy.<br /><br /><br />[Just gotta be careful not to hit <a href="http://en.wikipedia.org/wiki/Resonance">resonant</a> <a href="http://en.wikipedia.org/wiki/Coma">sleepquency</a>]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0tag:blogger.com,1999:blog-14769935.post-80096548223573240242011-12-28T17:08:00.000-08:002012-12-03T13:15:38.408-08:00Gift WrappingIn these times of austerity, one has to be economical with one's wrapping paper.<br /><br />Yeah, I know, this would have been much more useful about a week ago. But I just never got around to it. And I use the word 'useful' loosely; any saving one might get from these 'techniques' will most likely be insignificant.<br /><br />Nonetheless...<br /><br /><br /><b>Rectangles</b><br /><br />All gifts - no matter shape or size - can be wrapped with a rectangular piece of paper.<br /><br />This is easily proved. However doing so may not be the neatest or most efficient approach. For example, it's easy to efficiently wrap a cuboid (book, DVD, box) with a rectangular piece of paper, it's less easy to wrap a bike (efficiently) with a single, and in this case, very large piece. However, in the case of a bike, one could use several, smaller pieces of paper for more efficient wrapping.<br /><br />For a ball, one might be tempted to say that a more unusual shape - maybe a <a href="http://en.wikipedia.org/wiki/Truncated_icosahedron">truncated icosahedral</a> shell - would be more efficient. However, cutting out such a shape would result in lots of little slivers of waste, and would take a lot of time and effort. Instead, it's usually easier, and not greatly inefficient to just use a rectangular piece, and some creative folding and scrunching.<br /><br />So here's the point - for a given gift, we can define a rectangle (or set of rectangles) which most efficiently wraps that gift.<br /><br />For a cuboid of sides L(ength), W(idth), H(eight), the minimum paper would be: <i>(H+L)</i> by <i>2(H+W)</i>, with a bit extra for overlap.<br /><br />For a cylinder of dimensions H(eight) and R(adius), the paper would need to be: <i>2PI*R</i> by <i>(H+2R)</i>, and a bit.<br /><br />[<i>Proof that these cuts are optimal is left as an exercise for the reader.</i>] <br /><br />For other shapes, this may be more tricky to determine, but for the sake of arguing, we will assume we know all the required rectangle's dimensions.<br /><br /><br /><b>Bin Packing</b><br /><br />So for a given collection of gifts to be wrapped, we have defined a set of rectangles. We now have to determine the most efficient way to cut these out from rolls of wrapping paper - i.e. a large rectangle, ~ 1m x 4m.<br /><br />This is similar to solving a two-dimensional version of the <a href="http://en.wikipedia.org/wiki/Bin_packing_problem">Bin Packing problem</a>.<br /><br />In the Bin Packing problem, we have several bins (cuboids) of fixed dimensions, and a collection of smaller cuboidal objects. The problem is to pack the objects into as few bins a possible - i.e. find an efficient way of fitting cuboids into boxes.<br /><br />The problem is <a href="http://en.wikipedia.org/wiki/NP-hard">NP-Hard</a>, meaning that finding the optimal solution can take a very long time. However, there are algorithms which are fast and give <a href="http://en.wikipedia.org/wiki/Approximation_algorithm">adequately optimal</a> solutions.<br /><br />One, in particular, is called the First Fit algorithm, and it works something like this:<br /><br />1) Sort the rectangles into size order<br />2) Cut out the largest pieces still in the set<br />2 i) If there is not enough paper left on the current roll, move on to the next/start a new roll<br />3) Repeat (in descending size order) until there are no more pieces left to cut.<br /><br />There are also instructions for how to place the rectangles, for example first fit from bottom and right, to left and top.<br /><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-Mmg12GMyatQ/UL0VyvuDxaI/AAAAAAAABBA/RAk3HcImtsg/s1600/sort-wrap.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-Mmg12GMyatQ/UL0VyvuDxaI/AAAAAAAABBA/RAk3HcImtsg/s1600/sort-wrap.png" /></a></div>This <a href="http://www.codeproject.com/KB/web-image/rectanglepacker.aspx">article</a> gives a more precise description of a similar algorithm, though that one is defined for a 'roll' of non-restricted dimensions.<br /><br /><br /><b>Realistically</b><br /><br />That's all well and good mathematically. But in the real world, it's likely more trouble to solve the problem than the saving in paper is worth.<br /><br />However, the general principle of the algorithm can be loosely applied (if indeed it isn't the approach you already use) to improve on efficiency.<br /><br />In general, you can easily sight-guess which gift in going to require the largest amount of paper. And from that you can apply the First Fit algorithm.<br /><br /><div class="separator" style="clear: both; text-align: left;">One change I make is, cut out and wrap the largest one left, first. Then wrap the gift(s) which best fits the off-cut.</div><div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-z-K2CgqAjvc/UL0WAIv5l3I/AAAAAAAABBI/fTKCfL0Kq60/s1600/offcut-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://1.bp.blogspot.com/-z-K2CgqAjvc/UL0WAIv5l3I/AAAAAAAABBI/fTKCfL0Kq60/s1600/offcut-diagram.png" /></a></div>This is particularly good for rolls, where you want to unroll and use it a 'slice' at a time (rather than unrolling the whole thing).<br /><br /><br />Anyway, something to think about next time you happen to be wrapping a large number of gifts.<br /><br />Even better if you can get some of that Tesco wrapping paper with the grid on the back.<br /><br /><br />Oatzy.<br /><br /><br />[No wasted paper from my wrapping this year. Just saying.]Oatzyhttp://www.blogger.com/profile/07766533850640317524noreply@blogger.com0