Browse files

Merge branch 'master' into interactive_tutorial

Conflicts:
	.gitignore
	tox.ini
  • Loading branch information...
2 parents fdf7929 + 1d24472 commit d4c0880f59062b2c3d14c9a50c879d4db6b3b931 @timo committed May 6, 2012
Showing with 724 additions and 464 deletions.
  1. +0 −2 .gitignore
  2. +0 −7 README
  3. BIN docs/_templates/logo.png
  4. +37 −0 docs/gallery.rst
  5. BIN docs/gallery/zasim_gallery_1.png
  6. BIN docs/gallery/zasim_gallery_2.png
  7. BIN docs/gallery/zasim_gallery_3.png
  8. BIN docs/gallery/zasim_gallery_4.png
  9. BIN docs/gallery/zasim_gallery_5.png
  10. BIN docs/gallery/zasim_gallery_6.png
  11. +11 −0 docs/index.rst
  12. +6 −1 docs/tutorial/simulator_without_cagen.rst
  13. BIN images/vonNeumann/C00.jpg
  14. BIN images/vonNeumann/C00.png
  15. BIN images/vonNeumann/C01.jpg
  16. BIN images/vonNeumann/C01.png
  17. BIN images/vonNeumann/C10.jpg
  18. BIN images/vonNeumann/C10.png
  19. BIN images/vonNeumann/C11.jpg
  20. BIN images/vonNeumann/C11.png
  21. BIN images/vonNeumann/S.jpg
  22. BIN images/vonNeumann/S.png
  23. BIN images/vonNeumann/S0.jpg
  24. BIN images/vonNeumann/S0.png
  25. BIN images/vonNeumann/S00.jpg
  26. BIN images/vonNeumann/S00.png
  27. BIN images/vonNeumann/S000.jpg
  28. BIN images/vonNeumann/S000.png
  29. BIN images/vonNeumann/S01.jpg
  30. BIN images/vonNeumann/S01.png
  31. BIN images/vonNeumann/S1.jpg
  32. BIN images/vonNeumann/S1.png
  33. BIN images/vonNeumann/S10.jpg
  34. BIN images/vonNeumann/S10.png
  35. BIN images/vonNeumann/S11.jpg
  36. BIN images/vonNeumann/S11.png
  37. BIN images/vonNeumann/T000.jpg
  38. BIN images/vonNeumann/T000.png
  39. BIN images/vonNeumann/T001.jpg
  40. BIN images/vonNeumann/T001.png
  41. BIN images/vonNeumann/T010.jpg
  42. BIN images/vonNeumann/T010.png
  43. BIN images/vonNeumann/T011.jpg
  44. BIN images/vonNeumann/T011.png
  45. BIN images/vonNeumann/T020.jpg
  46. BIN images/vonNeumann/T020.png
  47. BIN images/vonNeumann/T021.jpg
  48. BIN images/vonNeumann/T021.png
  49. BIN images/vonNeumann/T030.jpg
  50. BIN images/vonNeumann/T030.png
  51. BIN images/vonNeumann/T031.jpg
  52. BIN images/vonNeumann/T031.png
  53. BIN images/vonNeumann/T100.jpg
  54. BIN images/vonNeumann/T100.png
  55. BIN images/vonNeumann/T101.jpg
  56. BIN images/vonNeumann/T101.png
  57. BIN images/vonNeumann/T110.jpg
  58. BIN images/vonNeumann/T110.png
  59. BIN images/vonNeumann/T111.jpg
  60. BIN images/vonNeumann/T111.png
  61. BIN images/vonNeumann/T120.jpg
  62. BIN images/vonNeumann/T120.png
  63. BIN images/vonNeumann/T121.jpg
  64. BIN images/vonNeumann/T121.png
  65. BIN images/vonNeumann/T130.jpg
  66. BIN images/vonNeumann/T130.png
  67. BIN images/vonNeumann/T131.jpg
  68. BIN images/vonNeumann/T131.png
  69. BIN images/vonNeumann/U.jpg
  70. BIN images/vonNeumann/U.png
  71. +5 −0 test/conftest.py
  72. +7 −3 test/test_config.py
  73. +5 −0 test/test_display.py
  74. +75 −0 test/test_display_qt.py
  75. +4 −0 test/test_dualrule.py
  76. +25 −14 test/test_gui.py
  77. +1 −0 test/test_sparse.py
  78. +2 −5 tox.ini
  79. +7 −0 zasim/cagen/computations.py
  80. +55 −145 zasim/cagen/jvn.py
  81. +48 −6 zasim/config.py
  82. +27 −13 zasim/display/console.py
  83. +238 −55 zasim/display/qt.py
  84. +1 −1 zasim/examples/silly/main.py
  85. +5 −1 zasim/examples/turing/main.py
  86. +19 −0 zasim/examples/turing/test_turing.py
  87. +2 −2 zasim/external/qt.py
  88. +10 −1 zasim/features.py
  89. +0 −203 zasim/gui/correct_silly_main.py
  90. +5 −2 zasim/gui/display.py
  91. +114 −2 zasim/gui/displaywidgets.py
  92. +1 −1 zasim/gui/elementary.py
  93. +3 −0 zasim/gui/mainwin.py
  94. BIN zasim/idle.png
  95. +11 −0 zasim/simulator.py
View
2 .gitignore
@@ -8,7 +8,5 @@ coverage.xml
htmlcov
junit*.xml
.ropeproject
-docs/_templates/logo.png
-zasim/idle.png
build/
dist/
View
7 README
@@ -24,13 +24,6 @@ or with
cd docs
make html
-In order to get a complete documentation, you still need to generate the logo
-image from the svg using inkscape with the following commands:
-
-# create the compilation animation picture like this:
-inkscape -f docs/logo.svg -i snake -j -C -e zasim/idle.png
-# create the logo for the documentation with this command:
-inkscape -e docs/_templates/logo.png -C -w 175 -h 175 docs/logo.svg
Visit the homepage of zasim at http://zasim.wakelift.de/, which is basically
just the generated html documentation put up on a webserver.
View
BIN docs/_templates/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
37 docs/gallery.rst
@@ -0,0 +1,37 @@
+.. _screenshot_gallery:
+
+Screenshot gallery
+==================
+
+.. figure:: gallery/zasim_gallery_1.png
+ :alt: Running a four-color one-dimensional CA
+
+ Running a four-color one-dimensional CA with a histogram, activity chart and the basic rule editor open.
+
+
+.. figure:: gallery/zasim_gallery_2.png
+ :alt: Running Conway's Game of Life
+
+ Running Conway's Game of Life.
+
+
+.. figure:: gallery/zasim_gallery_3.png
+ :alt: Game of Life with nondeterministic step function
+
+ Running Game of Life with a nondeterministic step function gives mazes/noodles.
+
+.. figure:: gallery/zasim_gallery_4.png
+ :alt: zasim in the interactive ipython qtconsole
+
+ Using the ipython qtconsole, you can display configurations inline.
+
+.. figure:: gallery/zasim_gallery_5.png
+ :alt: zasim gui elements in the interactive ipython qtconsole
+
+ IPythons magic `%gui qt` command will run an event loop allowing us to use qt widgets like the ones zasim offers at the same time as running code.
+
+.. figure:: gallery/zasim_gallery_6.png
+ :alt: Interactive tutorial
+
+ Zasim comes with a IPython notebook based interactive tutorial.
+
View
BIN docs/gallery/zasim_gallery_1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN docs/gallery/zasim_gallery_2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN docs/gallery/zasim_gallery_3.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN docs/gallery/zasim_gallery_4.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN docs/gallery/zasim_gallery_5.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN docs/gallery/zasim_gallery_6.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
11 docs/index.rst
@@ -22,6 +22,17 @@ The zasim code can be `found on github <http://github.com/timo/zasim/>`_.
Additionally, this documentation is built with "view source" links for
the files.
+Examples
+========
+
+Have a look at the :ref:`Example Gallery <screenshot_gallery>` for a few
+interesting things zasim can do.
+
+.. toctree::
+ :hidden:
+
+ gallery
+
Running zasim
=============
View
7 docs/tutorial/simulator_without_cagen.rst
@@ -150,6 +150,11 @@ list for a `~zasim.config.RandomInitialConfiguration`::
# call parents init so that Qt Signals can work
super(TuringTapeSimulator, self).__init__()
+ # use the palette for a character-boxes based renderer
+ self.palette_info = {
+ 'cboxes': palette
+ }
+
# set the size of the tape, as per the Simulator interface.
self.shape = (25,)
self.cconf = RandomInitialConfiguration(12, *probabs).generate((self.shape))
@@ -173,7 +178,7 @@ When the `TuringTapeSimulator` class is done, with its step function from above,
can be used quite simply::
tape = TuringTapeSimulator()
- painter = MultilineOneDimConsolePainter(tape, palette, compact_boxes=True)
+ painter = MultilineOneDimConsolePainter(tape, compact_boxes=True)
painter.after_step()
print
View
BIN images/vonNeumann/C00.jpg
Deleted file not rendered
View
BIN images/vonNeumann/C00.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/C01.jpg
Deleted file not rendered
View
BIN images/vonNeumann/C01.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/C10.jpg
Deleted file not rendered
View
BIN images/vonNeumann/C10.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/C11.jpg
Deleted file not rendered
View
BIN images/vonNeumann/C11.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/S.jpg
Deleted file not rendered
View
BIN images/vonNeumann/S.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/S0.jpg
Deleted file not rendered
View
BIN images/vonNeumann/S0.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/S00.jpg
Deleted file not rendered
View
BIN images/vonNeumann/S00.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/S000.jpg
Deleted file not rendered
View
BIN images/vonNeumann/S000.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/S01.jpg
Deleted file not rendered
View
BIN images/vonNeumann/S01.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN images/vonNeumann/S1.jpg
Diff not rendered.
View
BIN images/vonNeumann/S1.png
Diff not rendered.
View
BIN images/vonNeumann/S10.jpg
Diff not rendered.
View
BIN images/vonNeumann/S10.png
Diff not rendered.
View
BIN images/vonNeumann/S11.jpg
Diff not rendered.
View
BIN images/vonNeumann/S11.png
Diff not rendered.
View
BIN images/vonNeumann/T000.jpg
Diff not rendered.
View
BIN images/vonNeumann/T000.png
Diff not rendered.
View
BIN images/vonNeumann/T001.jpg
Diff not rendered.
View
BIN images/vonNeumann/T001.png
Diff not rendered.
View
BIN images/vonNeumann/T010.jpg
Diff not rendered.
View
BIN images/vonNeumann/T010.png
Diff not rendered.
View
BIN images/vonNeumann/T011.jpg
Diff not rendered.
View
BIN images/vonNeumann/T011.png
Diff not rendered.
View
BIN images/vonNeumann/T020.jpg
Diff not rendered.
View
BIN images/vonNeumann/T020.png
Diff not rendered.
View
BIN images/vonNeumann/T021.jpg
Diff not rendered.
View
BIN images/vonNeumann/T021.png
Diff not rendered.
View
BIN images/vonNeumann/T030.jpg
Diff not rendered.
View
BIN images/vonNeumann/T030.png
Diff not rendered.
View
BIN images/vonNeumann/T031.jpg
Diff not rendered.
View
BIN images/vonNeumann/T031.png
Diff not rendered.
View
BIN images/vonNeumann/T100.jpg
Diff not rendered.
View
BIN images/vonNeumann/T100.png
Diff not rendered.
View
BIN images/vonNeumann/T101.jpg
Diff not rendered.
View
BIN images/vonNeumann/T101.png
Diff not rendered.
View
BIN images/vonNeumann/T110.jpg
Diff not rendered.
View
BIN images/vonNeumann/T110.png
Diff not rendered.
View
BIN images/vonNeumann/T111.jpg
Diff not rendered.
View
BIN images/vonNeumann/T111.png
Diff not rendered.
View
BIN images/vonNeumann/T120.jpg
Diff not rendered.
View
BIN images/vonNeumann/T120.png
Diff not rendered.
View
BIN images/vonNeumann/T121.jpg
Diff not rendered.
View
BIN images/vonNeumann/T121.png
Diff not rendered.
View
BIN images/vonNeumann/T130.jpg
Diff not rendered.
View
BIN images/vonNeumann/T130.png
Diff not rendered.
View
BIN images/vonNeumann/T131.jpg
Diff not rendered.
View
BIN images/vonNeumann/T131.png
Diff not rendered.
View
BIN images/vonNeumann/U.jpg
Diff not rendered.
View
BIN images/vonNeumann/U.png
Diff not rendered.
View
5 test/conftest.py
@@ -0,0 +1,5 @@
+# make py.test use coredumps.
+
+def pytest_sessionstart(*args, **kwargs):
+ import resource
+ resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
View
10 test/test_config.py
@@ -3,7 +3,10 @@
from .testutil import assert_arrays_equal
from zasim import config
-from zasim.features import HAVE_MULTIDIM
+from zasim.features import HAVE_MULTIDIM, HAVE_DTYPE_AS_INDEX
+
+import sys
+IS_PYPY = "pypy_version_info" in dir(sys)
import pytest
@@ -98,6 +101,7 @@ def test_random_2d_probabilities(self):
assert (crr == 2).any()
@pytest.mark.skipif("not HAVE_MULTIDIM")
+ @pytest.mark.skipif("IS_PYPY")
def test_export_import_conf_ascii(self):
from zasim.cagen import ElementarySimulator
from zasim.display.console import TwoDimConsolePainter
@@ -111,7 +115,7 @@ def test_export_import_conf_ascii(self):
nconf_imp = config.AsciiInitialConfiguration(tmpfile.name)
assert_arrays_equal(nconf_imp.generate(), s.get_config())
- @pytest.mark.skipif("not HAVE_MULTIDIM")
+ @pytest.mark.skipif("IS_PYPY")
def test_export_import_conf_png(self, scale):
from zasim.cagen import GameOfLife
from zasim.display.qt import TwoDimQImagePainter
@@ -127,7 +131,6 @@ def test_export_import_conf_png(self, scale):
nconf_imp = config.ImageInitialConfiguration(tmpfile.name, scale=scale)
assert_arrays_equal(nconf_imp.generate(), s.get_config())
-
def test_probability_distribution_1d(self):
zero_fun = 1
one_fun = lambda x, w: 0 if x <= w/2 else 5
@@ -140,6 +143,7 @@ def test_probability_distribution_1d(self):
assert not conf[50:].all()
@pytest.mark.skipif("not HAVE_MULTIDIM")
+ @pytest.mark.xfail("not HAVE_DTYPE_AS_INDEX")
def test_probability_distribution_2d(self):
zero_fun = 1
one_fun = lambda x, y, w, h: 0 if x <= w/2 else 5
View
5 test/test_display.py
@@ -9,8 +9,12 @@
import pytest
import tempfile
+import sys
+IS_PYPY = "pypy_version_info" in dir(sys)
+
class TestDisplay:
@pytest.mark.skipif("not HAVE_MULTIDIM")
+ @pytest.mark.xfail("IS_PYPY")
def test_pretty_print_config_2d(self, capsys):
gconf = GLIDER[0]
simo = cagen.GameOfLife(config=gconf)
@@ -37,6 +41,7 @@ def test_pretty_print_config_2d(self, capsys):
"""
+ @pytest.mark.xfail("IS_PYPY")
def test_pretty_print_config_1d(self, capsys):
conf = np.array([1,0,1,1,0])
br = cagen.BinRule(config=conf, rule=204)
View
75 test/test_display_qt.py
@@ -0,0 +1,75 @@
+try:
+ from PySide.QtCore import *
+ from PySide.QtGui import *
+ from PySide.QtTest import *
+
+ app = qApp or QApplication([])
+ HAVE_QT = True
+except ImportError:
+ HAVE_QT = False
+
+if HAVE_QT:
+ from zasim.display.qt import *
+ from zasim.config import *
+ from zasim.cagen import jvn
+
+import pytest
+
+import numpy as np
+
+import sys
+import signal
+import traceback
+
+_exceptions = []
+def my_except_hook(cls, instance, traceback):
+ print "oh god, an exception!"
+ print cls
+ print instance
+ print traceback
+ print
+ traceback.print_exception(cls, instance, traceback)
+ _exceptions.append((cls, instance, traceback))
+
+_aborts = []
+def my_abort_hook():
+ print "oh god, sigabort!"
+ print
+ print traceback.print_stack()
+ print
+ _aborts.append(True)
+
+def fail_on_exceptions():
+ exc = _exceptions[:]
+ [_exceptions.remove(a) for a in exc]
+ aborts = _aborts[:]
+ [_aborts.remove(a) for a in aborts]
+ if exc:
+ pytest.fail("There were exceptions in the base.\n%s" % (exc[0]))
+ if aborts:
+ pytest.fail("There were abort signals in the tests.")
+
+def setup_module():
+ sys.excepthook = my_except_hook
+ signal.signal(signal.SIGABRT, my_abort_hook)
+
+def teardown_module():
+ sys.excepthook = sys.__excepthook__
+ signal.signal(signal.SIGABRT, signal.SIG_DFL)
+
+@pytest.mark.skipif("not HAVE_QT")
+class TestDisplayQt:
+ def test_tiled_display(self):
+ test_conf = RandomInitialConfigurationFromPalette(jvn.states)
+ conf = test_conf.generate((10, 10))
+
+ img = render_state_array_tiled(conf, jvn.PALETTE_JVN_IMAGE, jvn.PALETTE_JVN_RECT)
+
+ img2 = render_state_array_tiled(conf, jvn.PALETTE_JVN_IMAGE, jvn.PALETTE_JVN_RECT,region=(2, 2, 6, 6))
+
+ def test_tiled_display_1d(self):
+ test_conf = RandomInitialConfigurationFromPalette(jvn.states)
+ conf = test_conf.generate((10,))
+
+ img = render_state_array_tiled(conf, jvn.PALETTE_JVN_IMAGE, jvn.PALETTE_JVN_RECT)
+ img2 = render_state_array_tiled(conf, jvn.PALETTE_JVN_IMAGE, jvn.PALETTE_JVN_RECT,region=(2, 0, 6, 1))
View
4 test/test_dualrule.py
@@ -10,6 +10,7 @@
import pytest
class TestDualRule:
+ @pytest.mark.xfail("not HAVE_DTYPE_AS_INDEX")
def test_run_nondeterministic_pure(self):
# implement nazim fatès density classifier
compu = cagen.DualRuleCellularAutomaton(184, 232, 0.1)
@@ -30,6 +31,7 @@ def test_run_nondeterministic_weave(self):
for i in range(50):
simu.step_inline()
+ @pytest.mark.xfail("not HAVE_DTYPE_AS_INDEX")
def test_compare_nondeterministic_pure(self):
compu = cagen.DualRuleCellularAutomaton(184, 232, 0)
sf = cagen.automatic_stepfunc(size=(100,), computation=compu, histogram=True, needs_random_generator=True)
@@ -59,6 +61,7 @@ def test_compare_nondeterministic_weave(self):
assert_arrays_equal(simu.get_config(), br.get_config())
+ @pytest.mark.xfail("not HAVE_DTYPE_AS_INDEX")
def test_compare_evil_random_pure(self):
rando = ZerosThenOnesRandom(1000)
compu = cagen.DualRuleCellularAutomaton(184, 232, 0.5)
@@ -84,6 +87,7 @@ def test_compare_evil_random_pure(self):
assert_arrays_equal(simu.get_config(), br2.get_config())
+ @pytest.mark.xfail("not HAVE_DTYPE_AS_INDEX")
def test_dualrail_prettyprint(self):
compu = cagen.DualRuleCellularAutomaton(184, 232, 0.2)
sf = cagen.automatic_stepfunc(size=(100,), computation=compu, histogram=True, needs_random_generator=True)
View
39 test/test_gui.py
@@ -2,8 +2,6 @@
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtTest import *
-
- app = qApp or QApplication([])
HAVE_QT = True
except ImportError:
HAVE_QT = False
@@ -66,6 +64,19 @@ def teardown_module():
@pytest.mark.skipif("not HAVE_QT")
class TestGui:
+ def setup_method(self, method):
+ try:
+ self.app = QApplication([])
+ except RuntimeError:
+ self.app = QCoreApplication.instance()
+ self.app.setQuitOnLastWindowClosed(True)
+
+ def teardown_method(self, method):
+ self.app.closeAllWindows()
+ self.app.quit()
+ self.app.exit(0)
+ del self.app
+
def test_start_stop_binrule(self, size, base, scale, histogram):
print size, base, scale, histogram
sim_obj = cagen.ElementarySimulator(size, copy_borders=True, base=base, histogram=histogram)
@@ -85,20 +96,20 @@ def test_start_stop_binrule(self, size, base, scale, histogram):
QTest.mouseClick(display.control.start_button, Qt.LeftButton)
for execution in seconds(0.1):
- app.processEvents()
+ self.app.processEvents()
assert not display.control.start_button.isVisible()
for execution in seconds(0.1):
- app.processEvents()
+ self.app.processEvents()
QTest.mouseClick(display.control.stop_button, Qt.LeftButton)
for execution in seconds(0.1):
- app.processEvents()
+ self.app.processEvents()
assert not display.control.stop_button.isVisible()
- app.closeAllWindows()
+ self.app.closeAllWindows()
fail_on_exceptions()
def test_reset_button(self):
- sim_obj = cagen.ElementarySimulator((1000, 100), copy_borders=True, base=3)
+ sim_obj = cagen.ElementarySimulator((100, 100), copy_borders=True, base=3)
display = ZasimDisplay(sim_obj)
display.set_scale(1)
@@ -131,11 +142,11 @@ def test_reset_button(self):
def find_message_box(self, timeout=10):
end = time.time() + timeout
while time.time() < end:
- widgets = app.allWidgets()
+ widgets = self.app.allWidgets()
for widget in widgets:
if isinstance(widget, QMessageBox):
return widget
- app.processEvents()
+ self.app.processEvents()
def test_elementary_gui(self, base):
sim_obj = cagen.ElementarySimulator((10, 10), copy_borders=True, base=base)
@@ -153,9 +164,9 @@ def test_elementary_gui(self, base):
elementary_action.trigger()
for execution in seconds(0.1):
- app.processEvents()
+ self.app.processEvents()
- elementary_window = app.activeWindow()
+ elementary_window = self.app.activeWindow()
assert elementary_window is not None
actions = [act for act in elementary_window.findChildren(QPushButton)
@@ -164,9 +175,9 @@ def test_elementary_gui(self, base):
for action in actions:
QTest.mouseClick(action, Qt.LeftButton)
for execution in seconds(0.1):
- app.processEvents()
+ self.app.processEvents()
- app.closeAllWindows()
+ self.app.closeAllWindows()
fail_on_exceptions()
@@ -186,7 +197,7 @@ def test_animation(self):
anim = WaitAnimationWindow()
for execution in seconds(1):
- app.processEvents()
+ self.app.processEvents()
fail_on_exceptions()
View
1 test/test_sparse.py
@@ -35,6 +35,7 @@ def test_compare_sparse_life_weave(self):
self.body_compare_sparse_life(True)
@pytest.mark.skipif("not HAVE_MULTIDIM")
+ @pytest.mark.xfail("not HAVE_DTYPE_AS_INDEX")
def test_compare_sparse_life_pure(self):
self.body_compare_sparse_life(False)
View
7 tox.ini
@@ -2,14 +2,11 @@
envlist=py27,pypy,docs
[testenv]
deps=pytest
- pytest-cov
- numpy
-commands=py.test -v --doctest-modules --ignore=docs/vision.py --ignore=zasim/examples/notebooks --ignore=setup.py --junitxml=junit-{envname}.xml --cov zasim --cov-report xml
+commands=py.test -v --doctest-modules --ignore=docs/vision.py --ignore=setup.py --junitxml=junit-{envname}.xml
sitepackages=true
[testenv:pypy]
deps=pytest
- pytest-cov
-commands=py.test -v --doctest-modules --ignore=docs/vision.py --ignore=setup.py --ignore=zasim/examples/notebooks --ignore=zasim/gui --ignore=zasim/display/qt.py --ignore=zasim/external/qt.py --ignore=zasim/examples/silly --junitxml=junit-{envname}.xml --cov zasim --cov-report xml
+commands=py.test -v --doctest-modules --ignore=docs/vision.py --ignore=setup.py --ignore zasim/cagen/jvn.py --ignore=zasim/gui --ignore=zasim/display/qt.py --ignore=zasim/external/qt.py --ignore=zasim/examples/ --junitxml=junit-{envname}.xml
[testenv:docs]
basepython=python
deps=sphinx
View
7 zasim/cagen/computations.py
@@ -145,6 +145,13 @@ def pretty_print(self):
def build_name(self, parts):
if self.rule <= 255:
mingle = str
+ elif self.rule > 0xffffff:
+ def mingle(value):
+ raw = hex(value)
+ chunks, raw = [raw[0:6]], raw[6:]
+ for idx in range(len(raw)/4):
+ chunks.append(raw[idx * 4:idx * 4 + 4])
+ return " ".join(chunks)
else:
mingle = hex
parts.append("calculating rule %s" % (mingle(self.rule)))
View
200 zasim/cagen/jvn.py
@@ -8,6 +8,60 @@
import numpy as np
+## A map from states according to the bitmask to
+# pygame blittable states ( between 0 and 28 )
+displayableStateDict = {
+ 0: 0, # U
+ 2048: 1, #C00 2048
+ 2049: 2, #C10 2048+1
+ 2050: 3, #C01 2048+2
+ 2051: 4, #C11 2048+3
+ 4192: 5, #S000 4096+96
+ 4160: 6, #S00 4096+64
+ 4168: 7, #S01 4096+64+8
+ 4128: 8, #S0 4096+32
+ 4176: 9, #S10 4096+64+16
+ 4184: 10, #S11 4096+64+16+8
+ 4144: 11, #S1 4096+32+16
+ 4096: 12, #S 4096
+ 6144: 13, #T000 6144
+ 6272: 14, #T001 6144+128
+ 6400: 15, #T010 6144+256
+ 6528: 16, #T011 6144+128+256
+ 6656: 17, #T020 6144+512
+ 6784: 18, #T021 6144+128+512
+ 6912: 19, #T030 6144+256+512
+ 7040: 20, #T031 6144+128+256+512
+ 7168: 21, #T100 6144+1024
+ 7296: 22, #T101 6144+128+1024
+ 7424: 23, #T110 6144+256+1024
+ 7552: 24, #T111 6144+128+256+1024
+ 7680: 25, #T120 6144+512+1024
+ 7808: 26, #T121 6144+128+512+1024
+ 7936: 27, #T130 6144+256+512+1024
+ 8064: 28, #T131 6144+128+256+1024+512
+ }
+
+## A map from human readable vonNeumann states ( such as 'U', 'T020' and 'C11' )
+# actual states calculated via bitmask
+nameStateDict = { "U": 0,
+ #"C00" : 2048, "C10" : 2049, "C01" : 2050, "C11" : 2051,
+ "C00" : 1, "C10" : 2, "C01" : 3, "C11" : 4,
+ "S" : 4096, "S0" : 4128, "S1" : 4144, "S00" : 4160,
+ "S01" : 4168, "S10" : 4176, "S11" : 4184, "S000": 4192,
+ "T000": 6144, "T001": 6272, "T010": 6400, "T011": 6528,
+ "T020": 6656, "T032": 6784, "T030": 6912, "T031": 7040,
+ "T100": 7168, "T101": 7296, "T110": 7424, "T111": 7552,
+ "T120": 7680, "T121": 7808, "T130": 7936, "T131": 8064 }
+stateNameDict = {a:b for b,a in nameStateDict.iteritems()}
+states = sorted(nameStateDict.values())
+
+from os import path
+from zasim.display.qt import generate_tile_atlas
+
+# XXX get the absolute path if possible.
+filename_map = {num:path.join("images/vonNeumann", stateNameDict[num]) + ".png" for num in states}
+PALETTE_JVN_IMAGE, PALETTE_JVN_RECT = generate_tile_atlas(filename_map, "images/vonNeumann")
## The cellular automaton proposed by John von Neumann
# \verbatim
@@ -40,55 +94,6 @@ def __init__( self, sizeX, sizeY, confFile ):
self.dim = 2
self.size = self.sizeX, self.sizeY = sizeX, sizeY
- ## A map from states according to the bitmask to
- # pygame blittable states ( between 0 and 28 )
- self.displayableStateDict = {
- 0: 0, # U
- 2048: 1, #C00 2048
- 2049: 2, #C10 2048+1
- 2050: 3, #C01 2048+2
- 2051: 4, #C11 2048+3
- 4192: 5, #S000 4096+96
- 4160: 6, #S00 4096+64
- 4168: 7, #S01 4096+64+8
- 4128: 8, #S0 4096+32
- 4176: 9, #S10 4096+64+16
- 4184: 10, #S11 4096+64+16+8
- 4144: 11, #S1 4096+32+16
- 4096: 12, #S 4096
- 6144: 13, #T000 6144
- 6272: 14, #T001 6144+128
- 6400: 15, #T010 6144+256
- 6528: 16, #T011 6144+128+256
- 6656: 17, #T020 6144+512
- 6784: 18, #T021 6144+128+512
- 6912: 19, #T030 6144+256+512
- 7040: 20, #T031 6144+128+256+512
- 7168: 21, #T100 6144+1024
- 7296: 22, #T101 6144+128+1024
- 7424: 23, #T110 6144+256+1024
- 7552: 24, #T111 6144+128+256+1024
- 7680: 25, #T120 6144+512+1024
- 7808: 26, #T121 6144+128+512+1024
- 7936: 27, #T130 6144+256+512+1024
- 8064: 28, #T131 6144+128+256+1024+512
- }
-
- ## A map from human readable vonNeumann states ( such as 'U', 'T020' and 'C11' )
- # actual states calculated via bitmask
- self.nameStateDict = { "U": 0,
- "C00" : 2048, "C10" : 2049, "C01" : 2050, "C11" : 2051,
- "S" : 4096, "S0" : 4128, "S1" : 4144, "S00" : 4160,
- "S01" : 4168, "S10" : 4176, "S11" : 4184, "S000": 4192,
- "T000": 6144, "T001": 6272, "T010": 6400, "T011": 6528,
- "T020": 6656, "T032": 6784, "T030": 6912, "T031": 7040,
- "T100": 7168, "T101": 7296, "T110": 7424, "T111": 7552,
- "T120": 7680, "T121": 7808, "T130": 7936, "T131": 8064 }
-
- ## An array containing all correct states (see vonNeumann)
- self.states = [ 0, 2048, 2049, 2050, 2051, 4096, 4128, 4144, 4160, 4168,
- 4176, 4184, 4192, 6144, 6272, 6400, 6528, 6656, 6784,
- 6912, 7040, 7168, 7296, 7424, 7552, 7680, 7808, 7936, 8064 ]
## The current configuration is held here
# as usual, these two arrays contain the real configuration, that is used
@@ -111,27 +116,7 @@ def __init__( self, sizeX, sizeY, confFile ):
# between 0 and ~2^13, so we need a dict (see vonNeumann::displayableStateDict)
# to map the states to 0..28, so the Display-module can display states
# without knowing the difference
- self.displayConf = np.zeros( self.size, int)
-
-
- for imgFile in ( "images/vonNeumann/U.jpg", "images/vonNeumann/C00.jpg",
- "images/vonNeumann/C01.jpg", "images/vonNeumann/C10.jpg",
- "images/vonNeumann/C11.jpg", "images/vonNeumann/S000.jpg",
- "images/vonNeumann/S00.jpg", "images/vonNeumann/S01.jpg",
- "images/vonNeumann/S0.jpg", "images/vonNeumann/S10.jpg",
- "images/vonNeumann/S11.jpg", "images/vonNeumann/S1.jpg",
- "images/vonNeumann/S.jpg", "images/vonNeumann/T000.jpg",
- "images/vonNeumann/T001.jpg", "images/vonNeumann/T010.jpg",
- "images/vonNeumann/T011.jpg", "images/vonNeumann/T020.jpg",
- "images/vonNeumann/T021.jpg", "images/vonNeumann/T030.jpg",
- "images/vonNeumann/T031.jpg", "images/vonNeumann/T100.jpg",
- "images/vonNeumann/T101.jpg", "images/vonNeumann/T110.jpg",
- "images/vonNeumann/T111.jpg", "images/vonNeumann/T120.jpg",
- "images/vonNeumann/T121.jpg", "images/vonNeumann/T130.jpg",
- "images/vonNeumann/T131.jpg" ):
- img = None #pygame.image.load( imgFile ).convert()
- self.palette.append( img )
-
+ self.displayConf = np.zeros( self.size, int )
## Used to append cells to the list of cells to handle in the next step
def enlist( self, x, y ):
@@ -145,81 +130,6 @@ def enlist( self, x, y ):
self.cList[self.cCounter] = i
self.cCounter += 1
- #def click_on_cell( self, x, y, mousekey, mods ):
- #EPS = 128
- #SPECIAL = 1024
- #CSTATE = 2048
- #SSTATE = 4096
- #TSTATE = 6144
-
- #if x <= 0 or x >= self.sizeX-1 or y <= 0 or y >= self.sizeY-1:
- #return
- #state = self.states[self.displayConf[x][y]]
- #s = 0
-
- #if e.button == 1:
- ## T-states
- #s = TSTATE
- #if mods & pygame.KMOD_LCTRL:
- ## eps
- #if state & EPS == 0 and (state & TSTATE == TSTATE):
- ## to just insert a new eps without changing anything
- #self.currConf[x][y] = state+EPS
- #self.nextConf[x][y] = state+EPS
- #self.enlist(x,y)
- #return
- #s += EPS
- #if mods & pygame.KMOD_LSHIFT:
- ## u
- #s += SPECIAL
- #if state == 0:
- #for nbs in ( self.states[self.displayConf[x+1][y]],
- #self.states[self.displayConf[x][y-1]],
- #self.states[self.displayConf[x-1][y]],
- #self.states[self.displayConf[x][y+1]] ):
- #if ( nbs & TSTATE == TSTATE ) \
- #and ( ( mods & pygame.KMOD_LSHIFT == state & SPECIAL ) \
- #and ( mods & pygame.KMOD_LCTRL == state & EPS ) ):
- #s += nbs & 768
- #self.currConf[x][y] = s
- #self.nextConf[x][y] = s
- #self.enlist(x,y)
- #return
- #s += (((state&768)+256) & 768)
- #self.currConf[x][y] = s
- #self.nextConf[x][y] = s
- #self.enlist(x,y)
- #if e.button == 3:
- #if mods & pygame.KMOD_LCTRL:
- ## C-states
- #s = CSTATE
- #s += (((state&3)+1) & 3)
- #elif mods & pygame.KMOD_LSHIFT:
- ## S-states
- #if state == 0 or (state & SSTATE) != SSTATE:
- #s = SSTATE
- #self.currConf[x][y] = s
- #self.nextConf[x][y] = s
- #self.enlist(x,y)
- #return
- #sIdx = ( ( self.displayableStateDict[state] - 5 + 1 ) % 8 ) + 5
- #s = self.states[sIdx]
- #else:
- ## U-state
- #s = 0
- #self.currConf[x][y] = s
- #self.nextConf[x][y] = s
- #self.enlist(x,y)
-
- #def getConf( self ):
- #for i in range( 1, self.sizeX-1 ):
- #for j in range( 1, self.sizeY-1 ):
- #if self.displayableStateDict.has_key( self.currConf[i][j] ):
- #self.displayConf[i][j] = self.displayableStateDict[self.currConf[i][j]]
- #else:
- #print "Unkown state in cell", i, j, ":", self.currConf[i][j]
- #return self.displayConf
-
## Updates all cells using scipy.weave.inline
def updateAllCellsWeaveInline( self ):
#
View
54 zasim/config.py
@@ -11,7 +11,6 @@
allowed to dictate what size the configuration should have. This is important
especially for loading configurations from files.
-
"""
# This file is part of zasim. zasim is licensed under the BSD 3-clause license.
# See LICENSE.txt for details.
@@ -46,7 +45,7 @@ def generate(self, size_hint=None, dtype=np.dtype("i")):
:returns: A numpy array to be used as the configuration.
"""
-class RandomInitialConfiguration(BaseInitialConfiguration):
+class BaseRandomInitialConfiguration(BaseInitialConfiguration):
def __init__(self, base=2, *percentages):
"""Create a random initial configuration with values from 0 to base-1
inclusive and, if positional arguments are given, use the supplied
@@ -102,11 +101,54 @@ def generate(self, size_hint=None, dtype=np.dtype("i")):
arr = np.zeros(randoms.shape, dtype=dtype)
for pos in product(*[xrange(siz) for siz in size]):
- arr[pos] = min(idx for idx, perc in enumerate(self.cumulative_percentages)
+ arr[pos] = min(idx for idx, perc in self.cumulative_percentages
if randoms[pos] < perc)
return arr
+ def make_percentages_cumulative(self, percentages):
+ self.percentages = percentages
+ if len(self.percentages) > len(self.values):
+ raise ValueError("Cannot have more percentage values than values.")
+
+ rest = len(self.values) - len(self.percentages)
+ if self.percentages:
+ cumulative_percentages = [sum(self.percentages[:index + 1]) for index in range(len(self.percentages))]
+ else:
+ cumulative_percentages = [1.0 / len(self.values)]
+ rest -= 1
+
+ if cumulative_percentages[-1] > 1.0:
+ raise ValueError("Probabilities must not add up to more than 1.0")
+
+ rest_percentage = 1.0 - cumulative_percentages[-1]
+
+ for number in range(rest):
+ cumulative_percentages.append(cumulative_percentages[-1] + rest_percentage / rest)
+
+ if rest == 0 and cumulative_percentages[-1] != 1.0:
+ raise ValueError("Probabilities must add up to 1.0")
+
+ self.cumulative_percentages = list(zip(self.values, cumulative_percentages))
+
+class RandomInitialConfiguration(BaseRandomInitialConfiguration):
+ def __init__(self, base=2, *percentages):
+ """Create a random initial configuration with values from 0 to base-1
+ inclusive and, if positional arguments are given, use the supplied
+ percentages for the different states."""
+
+ self.values = range(base)
+ self.make_percentages_cumulative(percentages)
+
+class RandomInitialConfigurationFromPalette(BaseRandomInitialConfiguration):
+ def __init__(self, values, *percentages):
+ """Create a random initial configuration with the given values and,
+ if positional arguments are given, use the supplied
+ percentages for the different states."""
+
+ self.values = values
+ self.make_percentages_cumulative(percentages)
+
class AsciiInitialConfiguration(BaseInitialConfiguration):
"""Import an ascii-based file with a palette, as generated by
`zasim.display.console.BaseConsolePainter.export`."""
@@ -119,8 +161,8 @@ def __init__(self, filename, palette=None):
self.filename = filename
if not palette:
- from zasim.display.console import BaseConsolePainter
- palette = BaseConsolePainter.PALETTE
+ from zasim.display import console
+ palette = console.PALETTE
if isinstance(palette, list):
palette = dict(enumerate(palette))
self.palette = palette
@@ -134,7 +176,7 @@ def generate(self, size_hint=None, dtype=np.dtype("i")):
result = np.empty((len(lines), len(lines[0])), dtype=dtype)
for value, entry in self.palette.iteritems():
result[whole_conf == entry] = value
- return result.T
+ return result.transpose()
class ImageInitialConfiguration(BaseInitialConfiguration):
"""Import an image file as a configuration."""
View
40 zasim/display/console.py
@@ -2,13 +2,14 @@
from ..simulator import QObject
+NO_DATA = " "
+PALETTE = [" ", "#", "-", ";", ",", "^", "+", "Y"]
+HTML_PALETTE = "#000 #fff #f00 #00f #0f0 #ff0 #0ff #f0f".split(" ")
+
class BaseConsolePainter(QObject):
"""This is a base class for implementing renderers that output the
configuration of a simulator as an ascii-art string."""
- NO_DATA = " "
- PALETTE = [" ", "#", "-", ";", ",", "^", "+", "Y"]
- HTML_PALETTE = "#000 #fff #f00 #00f #0f0 #ff0 #0ff #f0f".split(" ")
def __init__(self, simulator, extra=None, connect=True, auto_output=True, **kwargs):
"""Initialise the painter.
@@ -23,6 +24,18 @@ def __init__(self, simulator, extra=None, connect=True, auto_output=True, **kwar
self._last_conf = None
self._auto_output = auto_output
+ if 'chars' in self._sim.palette_info:
+ self.palette = self._sim.palette_info['chars']
+ else:
+ self.palette = PALETTE
+ self._sim.palette_info['chars'] = self.palette
+
+ if 'hexcols' in self._sim.palette_info:
+ self.html_palette = self._sim.palette_info['hexcols']
+ else:
+ self.html_palette = HTML_PALETTE
+ self._sim.palette_info['hexcols'] = self.html_palette
+
if connect:
self.connect_simulator()
@@ -55,7 +68,7 @@ def _repr_html_(self):
</table>"""
def line_to_html(data):
return ('<tr><td style="width: 10px; height: 10px; background: ' +
- '">&nbsp;</td><td style="width: 10px; height: 10px; background: '.join(self.HTML_PALETTE[self.PALETTE.index(value)] for value in data) +
+ '">&nbsp;</td><td style="width: 10px; height: 10px; background: '.join(self.html_palette[self.palette.index(value)] for value in data) +
'">&nbsp;</td></tr>')
content = "\n".join(line_to_html(line) for line in self._data)
@@ -70,12 +83,12 @@ def __init__(self, simulator, lines, **kwargs):
super(OneDimConsolePainter, self).__init__(simulator, **kwargs)
self._lines = lines
- self._data = [self.NO_DATA * self._sim.shape[0]]
+ self._data = [NO_DATA * self._sim.shape[0]]
self.after_step()
def draw_conf(self, update_step=True):
- newline = "".join(self.PALETTE[value] for value in self._last_conf)
+ newline = "".join(self.palette[value] for value in self._last_conf)
if len(self._data) == self._lines and update_step:
self._data.pop(0)
elif not update_step:
@@ -87,23 +100,23 @@ def export(self, filename):
out.write("\n".join(self._data + [""]))
def conf_replaced(self):
- self._data = [self.NO_DATA * self._sim.shape[0]]
+ self._data = [NO_DATA * self._sim.shape[0]]
class TwoDimConsolePainter(BaseConsolePainter):
"""This painter always draws the most current config."""
def __init__(self, simulator, **kwargs):
super(TwoDimConsolePainter, self).__init__(simulator, **kwargs)
- self._data = [[self.NO_DATA * self._sim.shape[1]]
+ self._data = [[NO_DATA * self._sim.shape[1]]
for i in range(self._sim.shape[0])]
self.after_step()
def draw_conf(self, update_step=True):
self._data = []
for line in self._last_conf.T:
- newline = "".join(self.PALETTE[value] for value in line)
+ newline = "".join(self.palette[value] for value in line)
self._data.append(newline)
def __str__(self):
@@ -115,20 +128,21 @@ def export(self, filename):
class MultilineOneDimConsolePainter(BaseConsolePainter):
"""A painter for multiline palettes (as described in `convert_palette`)."""
- def __init__(self, simulator, palette=None, compact_boxes=None, **kwargs):
+ def __init__(self, simulator, compact_boxes=None, **kwargs):
""":param simulator: The simulator to get configs from.
- :param palette: The palette to use. If none is supplied, a simple
- palette with boxes will be created.
:param compact_boxes: If this parameter is True, boxart palettes will
share borders for a more compact display."""
super(MultilineOneDimConsolePainter, self).__init__(simulator, **kwargs)
- if palette is None:
+ if 'cboxes' not in self._sim.palette_info:
box_contents = map(str, simulator.t.possible_values)
palette = self.convert_palette(self.box_art_palette([box_contents]))
if compact_boxes is None:
compact_boxes = True
+ self._sim.palette_info['cboxes'] = palette
+ else:
+ palette = self._sim.palette_info['cboxes']
self.palette = palette
self.palette_height = len(palette.values()[0])
View
293 zasim/display/qt.py
@@ -8,43 +8,106 @@
from __future__ import absolute_import
from ..external.qt import (QObject, QPixmap, QImage, QPainter, QPoint, QSize, QRect,
- QLine, QColor, QBuffer, QIODevice, Signal, Qt)
+ QPen, QBrush, QLine, QColor, QBuffer, QIODevice, Signal, Qt)
import numpy as np
import time
+import math
import Queue
+from itertools import product
+
+def generate_tile_atlas(filename_map, common_prefix=""):
+ """From a mapping to state value to filename, create a texture atlas
+ from the given filenames. Those should all be as big as the first one.
+
+ :returns: The tile atlas as a QPixmap and a mapping from value to a
+ QRect into the image.
+ """
+ # use the size of the first tile for every tile.
+ size = QImage(filename_map.values()[0]).rect()
+ one_w, one_h = size.width(), size.height()
+
+ # try to make the image as near to a square image a spossible
+ columns = int(math.ceil(math.sqrt(len(filename_map))))
+ rows = len(filename_map) / columns + 1
+
+ new_image = QPixmap(QSize(columns * one_w, rows * one_h))
+ palette_rect = {}
+
+ ptr = QPainter(new_image)
+ ptr.fillRect(new_image.rect(), QBrush("pink"))
+ for num, (value, name) in enumerate(filename_map.iteritems()):
+ img = QImage(name)
+
+ if img.isNull():
+ print "warning:", name, "not found."
+
+ # draw a bright error image with a bit of text
+ img = QImage(one_w, one_h, QImage.Format_RGB32)
+ img.fill(0xffff00ff)
+ errptr = QPainter(img)
+ errptr.setPen(QPen("white"))
+ fnt = errptr.font()
+ fnt.setPixelSize(30)
+ errptr.setFont(fnt)
+ name = name[len(common_prefix):] if name.startswith(common_prefix) else name
+ errptr.drawText(QRect(0, 0, one_w, one_h), Qt.AlignCenter, u"ERROR\nnot found:\n%s\n:(" % (name))
+ errptr.end()
+
+ position_rect = QRect(one_w * (num / rows), one_h * (num % rows), one_w, one_h)
+ ptr.drawImage(position_rect, img, img.rect())
+ #palette_pf[nameStateDict[name]] = lambda x, y: QPainter.PixmapFragment.create(
+ #QPointF(x, y),
+ #position_rect)
+ palette_rect[value] = position_rect
+
+ ptr.end()
+
+ return new_image, palette_rect
PALETTE_32 = [0xff000000, 0xffffffff, 0xffff0000, 0xff0000ff, 0xff00ff00, 0xffffff00, 0xff00ffff, 0xffff00ff]
def make_palette_qc(pal):
- result = []
- for color in pal:
+ result = {}
+ if isinstance(pal, list):
+ pal = dict(enumerate(pal))
+ for val, color in pal.iteritems():
b = color & 0xff
color = color >> 8
g = color & 0xff
color = color >> 8
r = color & 0xff
- result.append(QColor.fromRgb(r, g, b))
+ result[val] = QColor.fromRgb(r, g, b)
return result
def make_gray_palette(number):
"""Generates a grayscale with `number` entries.
+ Alternatively, accept a list or dictionary with values.
:returns: the RGB_32 palette and the QColor palette
"""
+ if isinstance(number, int):
+ keys = range(number)
+ elif isinstance(number, list):
+ number = len(list)
+ keys = range(number)
+ elif isinstance(number, dict):
+ number = len(list)
+ keys = number.keys()
+
if number - 1 > 0xff:
raise ValueError("cannot make 16bit grayscale with %d numbers" % number)
- pal_32 = []
- pal_qc = []
- for i in range(number):
+ pal_32 = {}
+ pal_qc = {}
+ for key, i in zip(keys, number):
perc = 1.0 * i / (number - 1)
of_32 = int(0xff * perc)
- pal_32.append(of_32 + (of_32 << 8) + (of_32 << 16))
- pal_qc.append(QColor.fromRgbF(perc, perc, perc))
+ pal_32[key] = of_32 + (of_32 << 8) + (of_32 << 16)
+ pal_qc[key] = QColor.fromRgbF(perc, perc, perc)
return pal_32, pal_qc
@@ -57,6 +120,85 @@ def qimage_to_pngstr(image):
buf.close()
return str(buf.data())
+_last_rendered_state_conf = None
+def render_state_array(states, palette=PALETTE_QC, invert=False, region=None):
+ global _last_rendered_state_conf
+ if region:
+ x, y, w, h = region
+ conf = states[x:x+w, y:y+h]
+ else:
+ x, y = 0, 0
+ w, h = states.shape
+ conf = states
+ nconf = np.empty((w - x, h - y), np.uint32, "F")
+
+ # XXX doesn't work with dictionary palettes yet.
+ if not invert:
+ nconf[conf==0] = palette[0]
+ nconf[conf==1] = palette[1]
+ else:
+ nconf[conf==1] = palette[0]
+ nconf[conf==0] = palette[1]
+
+ for num, value in enumerate(palette[2:]):
+ nconf[conf == num+2] = value
+
+ image = QImage(nconf.data, w - x, h - y, QImage.Format_RGB32)
+
+ # without this cheap trick, the data from the array is imemdiately freed and
+ # subsequently re-used, leading to the first pixels in the top left corner
+ # getting pretty colors and zasim eventually crashing.
+ _last_rendered_state_conf = nconf
+ return image
+
+def render_state_array_tiled(states, palette, rects, region=None, painter=None):
+ """Using a texture atlas and a dictionary of pixmap fragment "factories",
+ draw a configuration using graphical tiles.
+
+ :param states: The array of states to render.
+ :param palette: The image to use.
+ :param rects: A dictionary from state value to rect in the image.
+ :param region: What part of the config to render (x, y, w, h).
+ """
+
+ if region:
+ x, y, w, h = region
+ try:
+ conf = states[x:x+w, y:y+h]
+ except IndexError:
+ # this is 1d :(
+ assert h == 1
+ states = states.reshape((states.shape[0], h))
+ conf = states[x:x+w, y:y+h]
+
+ w, h = conf.shape
+ else:
+ x, y = 0, 0
+ try:
+ w, h = states.shape
+ except ValueError:
+ # the shape is 1d only
+ w, = states.shape
+ h = 1
+ states = states.reshape((w,h))
+ conf = states
+
+ if not painter:
+ tilesize = rects.values()[0].size()
+ result = QPixmap(QSize(w * tilesize.width(), h * tilesize.height()))
+ painter = QPainter(result)
+ painter.scale(tilesize.width(), tilesize.height())
+
+ positions = product(xrange(w), xrange(h))
+
+ values = [(pos, conf[pos]) for pos in positions]
+ fragments = [(QPoint(pos[0], pos[1]), rects[value]) for pos, value in values]
+
+ for dest, src in fragments:
+ painter.drawPixmap(QRect(dest, QSize(1, 1)), palette, src)
+
+ if not painter:
+ return result
class BaseQImagePainter(QObject):
"""This is a base class for implementing renderers for configs based on
@@ -94,6 +236,15 @@ def __init__(self, width, height, queue_size=1, scale=1,
self.desired_frame_duration = frame_duration
self.next_frame = 0
+ if 'colors32' not in self._sim.palette_info:
+ if len(self._sim.t.possible_values) > len(PALETTE_32):
+ self.palette = make_gray_palette(self._sim.t.possible_values)
+ else:
+ self.palette = PALETTE_32[:len(self._sim.t.possible_values)]
+ self._sim.palette_info['colors32'] = self.palette
+ else:
+ self.palette = self._sim.palette_info['colors32'][:len(self._sim.t.possible_values)]
+
if connect:
self.connect_simulator()
@@ -178,8 +329,6 @@ def __init__(self, simulator, lines=None, connect=True, **kwargs):
lines = simulator.shape[0]
self._last_step = 0
- self.palette = PALETTE_32[2:len(self._sim.t.possible_values)]
-
super(OneDimQImagePainter, self).__init__(
simulator.shape[0], lines, lines,
**kwargs)
@@ -193,7 +342,8 @@ def draw_conf(self):
confs_to_render = min(self._height - y, self._queued_steps)
- whole_conf = np.empty((confs_to_render, w), np.uint32, "C")
+ # create whole_conf lazily, so that we can derive the dtype from the confs
+ whole_conf = None
try:
while rendered < confs_to_render:
@@ -211,17 +361,17 @@ def draw_conf(self):
if peek is None:
raise
- nconf = whole_conf[rendered, ...]
+ if whole_conf is None:
+ whole_conf = np.zeros((w, confs_to_render), dtype=conf.dtype)
- if not self._invert_odd or self._odd:
- nconf[conf==0] = 0
- nconf[conf==1] = 0xffffff
- else:
- nconf[conf==0] = 0xffffff
- nconf[conf==1] = 0
+ # XXX this won't work with dictionary palettes
+ if (self._invert_odd and self._odd):
+ new_zeros = conf == 1
+ new_ones = conf == 0
+ conf[new_zeros] = 0
+ conf[new_ones] = 1
- for num, value in enumerate(self.palette):
- nconf[conf == num+2] = value
+ whole_conf[...,rendered] = conf
rendered += 1
@@ -232,16 +382,18 @@ def draw_conf(self):
except Queue.Empty:
pass
- if self._last_step % self._height == 0 and rendered:
- self.image_wrapped.emit()
+ if not rendered:
+ return
self._queued_steps -= rendered
- if rendered:
- _image = QImage(whole_conf.data, w, rendered, QImage.Format_RGB32)
- _image = _image.scaled(w * self._scale, rendered * self._scale)
+ _image = render_state_array(whole_conf, self.palette, False, (0, 0, w, rendered))
+ _image = _image.scaled(w * self._scale, rendered * self._scale)
+
+ painter = QPainter(self._image)
+ painter.drawImage(QPoint(0, y * self._scale), _image)
- painter = QPainter(self._image)
- painter.drawImage(QPoint(0, y * self._scale), _image)
+ if self._last_step % self._height == 0 and rendered:
+ self.image_wrapped.emit()
def after_step(self, update_step=True):
conf = self._sim.get_config().copy()
@@ -276,9 +428,23 @@ def conf_replaced(self):
self.after_step(False)
-class TwoDimQImagePainter(BaseQImagePainter):
+class TwoDimQImagePainterBase(BaseQImagePainter):
"""This class offers rendering a two-dimensional simulator config to
a QImage"""
+
+ def after_step(self, update_step=True):
+ if update_step and self.skip_frame():
+ return
+ conf = self._sim.get_config().copy()
+ self._queue.put((update_step, conf))
+
+ self.draw_conf()
+ self.update.emit(QRect(QPoint(0, 0), QSize(self._width, self._height)))
+
+ def create_image_surf(self):
+ pass
+
+class TwoDimQImagePainter(TwoDimQImagePainterBase):
def __init__(self, simulator, connect=True, **kwargs):
"""Initialise the TwoDimQImagePainter.
@@ -288,43 +454,54 @@ def __init__(self, simulator, connect=True, **kwargs):
self._sim = simulator
w, h = simulator.shape
- self.palette = PALETTE_32[2:len(self._sim.t.possible_values)]
super(TwoDimQImagePainter, self).__init__(w, h, queue_size=1, **kwargs)
def draw_conf(self):
try:
update_step, conf = self._queue.get_nowait()
w, h = self._width, self._height
- nconf = np.empty((w, h), np.uint32, "F")
-
- if not self._invert_odd or self._odd:
- nconf[conf==0] = 0
- nconf[conf==1] = 0xffffffff
- else:
- nconf[conf==1] = 0
- nconf[conf==0] = 0xffffffff
-
- for num, value in enumerate(self.palette):
- nconf[conf == num+2] = value
- image = QImage(nconf.data, w, h, QImage.Format_RGB32)
- image = QPixmap.fromImage(image)
+ image = render_state_array(conf, self.palette, self._invert_odd and self._odd, (0, 0, w, h))
+ pixmap = QPixmap.fromImage(image)
if self._scale != 1:
- image = image.scaled(w * self._scale, h * self._scale)
- self._image = image
+ pixmap = pixmap.scaled(w * self._scale, h * self._scale)
+ self._image = pixmap
if update_step:
self._odd = not self._odd
except Queue.Empty:
pass
- def after_step(self, update_step=True):
- if update_step and self.skip_frame():
- return
- conf = self._sim.get_config().copy()
- self._queue.put((update_step, conf))
+class TwoDimQImagePalettePainter(TwoDimQImagePainterBase):
+ def __init__(self, simulator, scale=0.1, **kwargs):
+ self._sim = simulator
- self.draw_conf()
- self.update.emit(QRect(QPoint(0, 0), QSize(self._width, self._height)))
+ if 'tiles' in self._sim.palette_info:
+ self.palette = self._sim.palette_info['tiles']['images']
+ self.rects = self._sim.palette_info['tiles']['rects']
+ else:
+ raise NotImplementedError("There is no default image palette yet.")
+
+ self.tile_size = rects.values()[0].height()
+ assert rects.values()[0].width() == self.tile_size
+
+ w, h = simulator.shape
+ w = w * self.tile_size
+ h = h * self.tile_size
+
+ super(TwoDimQImagePalettePainter, self).__init__(w, h, **kwargs)
+
+ def draw_conf(self):
+ try:
+ update_step, conf = self._queue.get_nowait()
+ tilesize = self.tile_size * self._scale
+ w, h = self._width / tilesize, self._height / tilesize
+
+ print self._width, self._height, tilesize
+ print w, h
+
+ self._image = render_state_array_tiled(conf, self.palette, self.rects)
+ except Queue.Empty:
+ pass
# TODO make a painter that continuously moves up the old configurations for saner
# display in ipython rich consoles and such.
@@ -341,8 +518,14 @@ def __init__(self, simulator, attribute, width, height, queue_size=1, connect=Tr
self._sim = simulator
self._attribute = attribute
self._linepos = 0
- self.palette = PALETTE_32[:len(self._sim.t.possible_values)]
- self.colors = make_palette_qc(self.palette)
+ if 'qcolors' not in self._sim.palette_info:
+ if 'colors32' in self._sim.palette_info:
+ self.colors = make_palette_qc(self._sim.palette_info['colors32'])
+ else:
+ raise ValueError("The simulator needs either qcolors or colors32 in its palette_info")
+ self._sim.palette_info['qcolors'] = self.colors
+ else:
+ self.colors = self._sim.palette_info['qcolors']
super(HistogramPainter, self).__init__(width, height, queue_size, connect=connect, **kwargs)
@@ -360,7 +543,7 @@ def draw_conf(self):
maximum = 1
scale = self._height * 1.0 / maximum
absolute = 0.0
- for value, color in zip(values, self.colors):
+ for value, color in zip(values, self.colors.values()):
value = value * scale
painter.setPen(color)
painter.drawLine(QLine(linepos, absolute,
View
2 zasim/examples/silly/main.py
@@ -163,7 +163,7 @@ def main(width=200, height=200, scale=2,
help="don't display a histogram")
argp.add_argument("--no-activity", default=True, action="store_false", dest="activity",
help="don't display the activity")
- argp.add_argument("--base", default=2, type=int,
+ argp.add_argument("--base", default=5, type=int,
help="How many colors to have.")
args = argp.parse_args()
View
6 zasim/examples/turing/main.py
@@ -16,6 +16,10 @@ class TuringTapeSimulator(SimulatorInterface):
def __init__(self):
super(TuringTapeSimulator, self).__init__()
+ self.palette_info = {
+ 'cboxes': palette
+ }
+
self.shape = (25,)
self.cconf = RandomInitialConfiguration(12, *probabs).generate((self.shape))
# do not send stuff from the right border.
@@ -51,7 +55,7 @@ def step(self):
def main():
tape = TuringTapeSimulator()
- painter = MultilineOneDimConsolePainter(tape, palette, compact_boxes=True)
+ painter = MultilineOneDimConsolePainter(tape, compact_boxes=True)
painter.after_step()
print
View
19 zasim/examples/turing/test_turing.py
@@ -0,0 +1,19 @@
+from zasim.examples.turing import main
+
+class TestTuringCa(object):
+ def test_turing_main(self):
+ main.main()
+
+ def test_turing_step(self):
+ tape = main.TuringTapeSimulator()
+ start_conf = tape.get_config().copy()
+ tape.step()
+ end_conf = tape.get_config().copy()
+
+ for index in range(tape.shape[0] - 1):
+ # if there's a signal in the field now, the data in the signal
+ # will have been in the top half of the field in the previous
+ # step.
+ if end_conf[index] % 4 != 3:
+ assert end_conf[index] % 4 == start_conf[index] / 4
+
View
4 zasim/external/qt.py
@@ -33,6 +33,6 @@
if "DISPLAY" in os.environ and os.environ["DISPLAY"]:
import sys
if WANT_GUI:
- app = QApplication(sys.argv)
+ app = qApp or QApplication(sys.argv)
if app is None:
- app = QApplication(sys.argv, False)
+ app = qApp or QApplication(sys.argv, False)
View
11 zasim/features.py
@@ -21,6 +21,9 @@
HAVE_NUMPY_RANDOM = True
"""Is numpy.random available?"""
+HAVE_DTYPE_AS_INDEX = True
+"""Can numpy dtypes be used as index to numpy arrays?"""
+
def tuple_array_index_fixup(line):
"""Remove tuple-indexing operations to numpy arrays.
@@ -70,6 +73,12 @@ def tuple_array_index_fixup(line):
def tuple_array_index_fixup(line):
return TUPLE_ACCESS_FIX.sub(r"\1", line)
+try:
+ import numpy as np
+ np.zeros((10,))[np.int8(5)]
+except TypeError:
+ HAVE_DTYPE_AS_INDEX = False
+
__all__ = ["HAVE_WEAVE", "HAVE_MULTIDIM", "HAVE_TUPLE_ARRAY_INDEX",
- "HAVE_BINCOUNT",
+ "HAVE_BINCOUNT", "HAVE_DTYPE_AS_INDEX",
"tuple_array_index_fixup"]
View
203 zasim/gui/correct_silly_main.py
@@ -1,203 +0,0 @@
-from .display import ZasimDisplay
-from .histogram import HistogramExtraDisplay
-
-from ..external.qt import Qt
-from .. import cagen
-
-import sys
-
-from zasim.cagen import *
-
-class SillyComputation(Computation):
- def visit(self):
- self.code.add_py_code("compute",
- """sup = max(%(name_one)s, %(name_two)s)
- second_sup = min(%(name_one)s, %(name_two)s)""" % dict(
- name_one=self.code.neigh.names[0],
- name_two=self.code.neigh.names[1]))
- # only create a loop if there are more than the 2 cells.
- if len(self.code.neigh.names) > 2:
- self.code.add_py_code("compute",
- """
- for val in [%(names)s]:
- if val > sup:
- second_sup, sup = sup, val
- elif val != sup and val > second_sup:
- second_sup = val""" % dict(
- names=",".join(self.code.neigh.names[2:])))
- # and finally, set the result value to be second_sup
- self.code.add_py_code("compute",
- """result = second_sup""")
-
- # we need at least the sup and second_sup variables
- self.code.add_weave_code("localvars",
- """int sup, second_sup;""")
- # initialise sup and second_sup from the first two neighbourhood cells
- self.code.add_weave_code("compute",
- """
- if (%(name_one)s > %(name_two)s) {
- sup = %(name_one)s;
- second_sup = %(name_two)s;
- } else if (%(name_one)s < %(name_two)s) {
- sup = %(name_two)s;
- second_sup = %(name_one)s;
- } else {
- sup = %(name_one)s;
- second_sup = -1;
- }""" % dict(name_one=self.code.neigh.names[0],
- name_two=self.code.neigh.names[1]))
- # if we have more neighbours, we simply loop over them
- if len(self.code.neigh.names) > 2:
- # in order to loop over the values in C, we create an array from them
- # the C compiler will probably completely optimise this away.
- self.code.add_weave_code("localvars",
- """int neigh_idx;""")
- self.code.add_weave_code("compute",
- """int neigh_arr[%d] = {%s};""" % (len(self.code.neigh.names) - 2,
- ", ".join(self.code.neigh.names[2:])))
- self.code.add_weave_code("compute",
- """
- for (neigh_idx = 0; neigh_idx < %(size)d; neigh_idx++) {
- if (neigh_arr[neigh_idx] > sup) {
- second_sup = sup;
- sup = neigh_arr[neigh_idx];
- } else if (neigh_arr[neigh_idx] > second_sup && neigh_arr[neigh_idx] < sup) {
- second_sup = neigh_arr[neigh_idx];
- }
- }""" % dict(size=len(self.code.neigh.names) - 2))
- self.code.add_weave_code("compute",
- """if (second_sup != -1) {
- result = second_sup; }
- else {
- result = sup;}""")
-
-class SillySim(CagenSimulator):
- def __init__(self, size=None, config=None, base=5, nondet=1, histogram=True, activity=False):
- if size is None:
- size = config.shape
-
- computer = SillyComputation()
- target = Target(size, config, base=base)
-
- neighbourhood = VonNeumannNeighbourhood()
- acc = SimpleStateAccessor()
- if nondet != 1:
- loop = TwoDimNondeterministicCellLoop(nondet)
- else:
- loop = TwoDimCellLoop()
- border = TwoDimSlicingBorderCopier()
-
- stepfunc = StepFunc(
- loop=loop,
- accessor=acc,
- neighbourhood=neighbourhood,
- extra_code=[border, computer] +
- ([SimpleHistogram()] if histogram else []) +
- ([ActivityRecord()] if activity else []), target=target)
- self.computer = computer
- stepfunc.gen_code()
-
- super(SillySim, self).__init__(stepfunc, target)
-
- def pretty_print(self):
- return "foo"
-
-def main(width=200, height=200, scale=2,
- onedim=False,
- beta=100, nondet=100,
- life=False, rule=None,
- copy_borders=True, white=50,
- histogram=True, activity=True,
- base=2):
-
- if white > 1:
- white = white / 100.
-
- beta = beta / 100.
- nondet = nondet / 100.
-
- w, h = width, height
-
- if onedim and not life:
- # get a random beautiful CA
- sim_obj = cagen.BinRule(rule=rule, size=(w,), nondet=nondet, beta=beta, activity=activity,
- histogram=histogram, copy_borders=copy_borders, base=base)
-
- else:
- if life:
- sim_obj = cagen.GameOfLife((w, h), nondet, histogram, activity, None, beta, copy_borders)
- else:
- #sim_obj = cagen.ElementarySimulator((w, h), nondet, histogram, activity, rule, None, beta, copy_borders, base=base)
- sim_obj = SillySim((w, h), base=base, nondet=nondet)
-
- #if not life:
- #print sim_obj.pretty_print()
- #print sim_obj.t.rule, hex(sim_obj.rule_number)
-
- display = ZasimDisplay(sim_obj)
- display.set_scale(scale)
-
- display.control.start()
-
- if histogram:
- extra_hist = HistogramExtraDisplay(sim_obj, parent=display, height=200, maximum= w * h)
- extra_hist.show()
- display.window.attach_display(extra_hist)
- display.window.addDockWidget(Qt.RightDockWidgetArea, extra_hist)
-
- if activity:
- extra_activity = HistogramExtraDisplay(sim_obj, attribute="activity", parent=display, height=200, maximum=w*h)
- extra_activity.show()
- display.window.attach_display(extra_activity)
- display.window.addDockWidget(Qt.RightDockWidgetArea, extra_activity)
-
- sys.exit(app.exec_())
-
-if __name__ == "__main__":
- import argparse
-
- argp = argparse.ArgumentParser(
- description="Run a 1d BinRule, a 2d Game of Life, or a 2d elementary "
- "cellular automaton")
- argp.add_argument("--onedim", default=False, action="store_true",
- help="generate a one-dimensional cellular automaton")
- argp.add_argument("--twodim", default=True, action="store_false", dest="onedim",
- help="generate a two-dimensional cellular automaton")
- argp.add_argument("--life", default=False, action="store_true",
- help="generate a conway's game of life - implies --twodim")
-
- argp.add_argument("-x", "--width", default=200, dest="width", type=int,
- help="the width of the image surface")
- argp.add_argument("-y", "--height", default=200, dest="height", type=int,
- help="the height of the image surface")
- argp.add_argument("-z", "--scale", default=3, dest="scale", type=int,
- help="the size of each cell of the configuration")
- argp.add_argument("-r", "--rule", default=None, type=str,
- help="the elementary cellular automaton rule number to use")
- argp.add_argument("-c", "--dont-copy-borders", default=True, action="store_false", dest="copy_borders",
- help="copy borders or just read zeros?")
- argp.add_argument("--white", default=20, type=int,
- help="what percentage of the cells to make white at the beginning.")
-
- argp.add_argument("--nondet", default=100, type=int,
- help="with what percentage should cells be executed?")
- argp.add_argument("--beta", default=100, type=int,
- help="with what probability should a cell succeed in exposing its "\
- "state to its neighbours?")
-
- argp.add_argument("--no-histogram", default=True, action="store_false", dest="histogram",
- help="don't display a histogram")
- argp.add_argument("--no-activity", default=True, action="store_false", dest="activity",
- help="don't display the activity")
- argp.add_argument("--base", default=2, type=int,
- help="The base of the cells.")
-
- args = argp.parse_args()
-
- if args.rule:
- if args.rule.startswith("0x"):
- args.rule = int(args.rule, 16)
- else:
- args.rule = int(args.rule)
-