{ "cells": [ { "cell_type": "markdown", "id": "44ef157c-8584-4acd-9a3c-3238f99911c3", "metadata": {}, "source": [ "1. **Run in a Jupyter Notebook:**\n", " - The interactive widgets require an environment that supports `ipywidgets`.\n", " - Ensure you have `ipywidgets` installed:\n", " ```bash\n", " pip install ipywidgets\n", " ```\n", " And enable widgets extensions:\n", " ```bash\n", " jupyter nbextension enable --py widgetsnbextension\n", " ```\n", "\n", "2. **Interactive Controls:**\n", " - **Shape Types:** Use the `SelectMultiple` widget to choose one or more shapes ('Circle', 'Square', 'Triangle').\n", " - **Plane Area (`A_plane`):** Adjust the total area of the plane to be filled.\n", " - **Initial Area (`A0`):** Set the area of the initial shape.\n", " - **Exponent (`p`):** Modify the exponent in the decreasing function \\( g(i) \\).\n", " - **Number of Shapes (`n_shapes`):** Set the maximum number of shapes to attempt to place.\n", " - **Max Particle Size (`max_particle_size`):** Specify the maximum size for the shapes.\n", " - **Random Orientation (`random_orientation`):** Toggle random rotations for squares and triangles.\n", "\n", "3. **Dynamic Updates:**\n", " - Adjust the controls to see the tiling update in real-time.\n", " - Observe how changing parameters affects the placement, size, and types of shapes.\n", "\n", "### Explanation of the New Features\n", "\n", "#### 1. Multiple Shape Types\n", "\n", "- **Implementation:**\n", " - Changed `shape_type` to `shape_types`, which is a list of selected shape types.\n", " - Used a `SelectMultiple` widget to allow multiple selections.\n", " - When assigning areas to shapes, the code randomly selects a shape type from the chosen list for each shape.\n", "\n", "- **Purpose:**\n", " - Allows you to include a mix of shapes in the tiling.\n", " - Adds diversity and complexity to the patterns generated.\n", "\n", "#### 2. Random Shape Selection\n", "\n", "- **Assignment of Areas:**\n", " - The areas calculated are shuffled to distribute different sizes among the shapes.\n", " - For each area, a shape type is randomly selected from the chosen list.\n", "\n", "- **Placement and Drawing:**\n", " - The code handles the drawing of each shape according to its type.\n", " - Overlap checking is adjusted to account for different shape types.\n", "\n", "#### 3. Overlap Checking Adjustments\n", "\n", "- **Mixed Shapes:**\n", " - The `is_overlapping` function is updated to handle different shape types.\n", " - Shapes are approximated as circles for overlap checking to simplify collision detection.\n", "\n", "- **Radius Calculation:**\n", " - For circles, the radius is straightforward.\n", " - For squares and triangles, the code calculates an equivalent radius by approximating the shape's bounding circle.\n", "\n", "### Additional Notes\n", "\n", "- **Shuffle Areas:**\n", " - Shuffling the list of areas ensures that sizes are randomly distributed among the shapes, preventing patterns where certain shapes only have large or small sizes.\n", "\n", "- **Shape-Specific Parameters:**\n", " - You can further customize the properties of each shape type if needed.\n", "\n", "- **Performance Considerations:**\n", " - As the complexity increases with multiple shapes, the computation time may also increase.\n", " - The code includes a maximum number of attempts to place each shape to prevent infinite loops.\n", "\n", "- **Visualization:**\n", " - The plot title dynamically updates to reflect the selected shape types.\n", " - Colors and styles are set differently for each shape type to distinguish them visually.\n", "\n", "### Example Usage\n", "\n", "- **Selecting All Shapes:**\n", " - By default, all shapes are selected, resulting in a tiling that includes circles, squares, and triangles.\n", "\n", "- **Selecting Specific Shapes:**\n", " - You can select only 'Circle' and 'Square' to exclude triangles.\n", " - Alternatively, select a single shape type to focus on that shape.\n", "\n", "- **Adjusting Parameters:**\n", " - Modify the exponent `p` to see how it affects the size distribution.\n", " - Change the maximum particle size to limit the size of the largest shapes.\n", " - Toggle random orientation to see the difference between aligned and randomly rotated shapes.\n", "\n", "**Note:** Ensure you have the necessary libraries installed (`numpy`, `matplotlib`, `scipy`, `ipywidgets`) and that you are running the code in an environment that supports interactive widgets.\n", "\n", "---" ] }, { "cell_type": "code", "execution_count": 1, "id": "067d9c00-aca2-4ef8-95c4-695e5989ca9a", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "048a1eaccc53497081b6db4f42270a03", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(Dropdown(description='Shape Type', options=('Circle', 'Square', 'Triangle'), value='Circ…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import matplotlib.patches as patches\n", "from scipy.special import zeta\n", "import random\n", "from ipywidgets import interact, FloatSlider, IntSlider, Dropdown, Checkbox\n", "\n", "# Function to generate the tiling\n", "def generate_tiling(\n", " shape_type='Circle',\n", " A_plane=100.0,\n", " A0=2,\n", " p=1.2,\n", " n_shapes=200000,\n", " max_particle_size=15.0,\n", " random_orientation=True\n", "):\n", " # Generate areas using Riemann zeta function power-law distribution\n", " # A_i = A0 / i^p, where the sum converges to A0 * zeta(p)\n", " zeta_p = zeta(p)\n", "\n", " # Generate areas for each shape: A_i = A0 / i^p\n", " areas = [A0] # First area (i=1)\n", " i = 2 # Start from i=2 since i=1 is already added\n", " while len(areas) < n_shapes:\n", " area = A0 / (i**p)\n", " if area <= 0 or np.sqrt(area) > max_particle_size:\n", " i += 1\n", " continue\n", " areas.append(area)\n", " i += 1\n", "\n", " # Compute dimensions of the shapes\n", " if shape_type == 'Circle':\n", " sizes = [np.sqrt(area / np.pi) for area in areas] # Radii\n", " else:\n", " sizes = [np.sqrt(area) for area in areas] # Side lengths for squares and triangles\n", "\n", " # Define the plane dimensions (square)\n", " L = np.sqrt(A_plane) # Length of the side of the square plane\n", "\n", " # Initialize list to store positions and sizes of the shapes\n", " positions = []\n", "\n", " # Function to check if a new shape overlaps with existing ones\n", " def is_overlapping(x_new, y_new, size_new, positions):\n", " for x, y, size, angle in positions:\n", " if shape_type == 'Circle':\n", " distance = np.hypot(x_new - x, y_new - y)\n", " if distance < (size_new + size):\n", " return True\n", " else:\n", " # Approximate the shapes with circles for overlap checking\n", " distance = np.hypot(x_new - x, y_new - y)\n", " radius_new = size_new * np.sqrt(2) / 2\n", " radius = size * np.sqrt(2) / 2\n", " if distance < (radius_new + radius):\n", " return True\n", " return False\n", "\n", " # Place the shapes randomly without overlapping\n", " for size in sizes:\n", " max_attempts = 1000\n", " for attempt in range(max_attempts):\n", " # Generate random position within the plane, ensuring the shape stays within bounds\n", " x = random.uniform(size, L - size)\n", " y = random.uniform(size, L - size)\n", " angle = random.uniform(0, 360) if random_orientation else 0\n", " # Check for overlap\n", " if not is_overlapping(x, y, size, positions):\n", " positions.append((x, y, size, angle))\n", " break\n", " else:\n", " print(f\"Could not place a shape of size {size} without overlap after {max_attempts} attempts.\")\n", " break\n", "\n", " # Plot the shapes\n", " fig, ax = plt.subplots(figsize=(8, 8))\n", " for x, y, size, angle in positions:\n", " if shape_type == 'Circle':\n", " shape = plt.Circle((x, y), size, edgecolor='blue', facecolor='lightblue', alpha=0.6)\n", " elif shape_type == 'Square':\n", " shape = patches.Rectangle(\n", " (x - size / 2, y - size / 2),\n", " size,\n", " size,\n", " angle=angle,\n", " edgecolor='green',\n", " facecolor='lightgreen',\n", " alpha=0.6\n", " )\n", " elif shape_type == 'Triangle':\n", " # Equilateral triangle\n", " h = size * np.sqrt(3) / 2\n", " points = np.array([\n", " [x, y + 2 * h / 3],\n", " [x - size / 2, y - h / 3],\n", " [x + size / 2, y - h / 3]\n", " ])\n", " t = patches.Polygon(\n", " points,\n", " closed=True,\n", " edgecolor='red',\n", " facecolor='salmon',\n", " alpha=0.6\n", " )\n", " shape = t\n", " shape.set_transform(\n", " plt.matplotlib.transforms.Affine2D().rotate_deg_around(x, y, angle) + ax.transData\n", " )\n", " else:\n", " continue\n", "\n", " if shape_type in ['Circle', 'Square']:\n", " shape.set_transform(\n", " plt.matplotlib.transforms.Affine2D().rotate_deg_around(x, y, angle) + ax.transData\n", " )\n", " ax.add_patch(shape)\n", " elif shape_type == 'Triangle':\n", " ax.add_patch(shape)\n", "\n", " # Set plot parameters\n", " ax.set_xlim(0, L)\n", " ax.set_ylim(0, L)\n", " ax.set_aspect('equal', 'box')\n", " ax.set_title(f'Iterative Tiling Using Riemann Zeta Function - {shape_type}s')\n", " ax.set_xlabel('X-axis')\n", " ax.set_ylabel('Y-axis')\n", " plt.grid(True)\n", " plt.show()\n", "\n", "# Interactive widget function\n", "def interactive_tiling(\n", " shape_type,\n", " A_plane,\n", " A0,\n", " p,\n", " n_shapes,\n", " max_particle_size,\n", " random_orientation\n", "):\n", " generate_tiling(\n", " shape_type=shape_type,\n", " A_plane=A_plane,\n", " A0=A0,\n", " p=p,\n", " n_shapes=n_shapes,\n", " max_particle_size=max_particle_size,\n", " random_orientation=random_orientation\n", " )\n", "\n", "# Create interactive sliders and dropdowns\n", "interact(\n", " interactive_tiling,\n", " shape_type=Dropdown(options=['Circle', 'Square', 'Triangle'], value='Circle', description='Shape Type'),\n", " A_plane=FloatSlider(value=100.0, min=50.0, max=500.0, step=10.0, description='Plane Area'),\n", " A0=FloatSlider(value=1.0, min=0.1, max=10.0, step=0.1, description='Initial Area A0'),\n", " p=FloatSlider(value=2.0, min=0.1, max=5.0, step=0.1, description='Exponent p'),\n", " n_shapes=IntSlider(value=200, min=50, max=100000, step=50, description='Number of Shapes'),\n", " max_particle_size=FloatSlider(value=5.0, min=0.5, max=20.0, step=0.5, description='Max Particle Size'),\n", " random_orientation=Checkbox(value=True, description='Random Orientation')\n", ")" ] }, { "cell_type": "code", "execution_count": 2, "id": "8083e0a0-f8af-41de-8805-d285905b4b43", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "69f346df5bfb435d82aa7efb72159884", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(SelectMultiple(description='Shape Types', index=(0, 1, 2, 3), options=('Circle', 'Square…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import matplotlib.patches as patches\n", "from scipy.special import zeta\n", "import random\n", "from ipywidgets import interact, FloatSlider, IntSlider, SelectMultiple, Checkbox, HBox, VBox, Label\n", "\n", "# Function to generate a random polygon\n", "def generate_polygon(center, avg_radius, irregularity, spikiness, num_vertices):\n", " '''Generates the points of a random polygon.\n", "\n", " Parameters:\n", " - center: tuple (x, y)\n", " - avg_radius: float, average radius of the polygon\n", " - irregularity: float between 0 and 1, the variance of the angular spacing\n", " - spikiness: float between 0 and 1, the variance of the radius\n", " - num_vertices: int, number of vertices of the polygon\n", "\n", " Returns:\n", " - A list of (x, y) tuples representing the vertices of the polygon.\n", " '''\n", " irregularity = np.clip(irregularity, 0, 1) * 2 * np.pi / num_vertices\n", " spikiness = np.clip(spikiness, 0, 1) * avg_radius\n", "\n", " # Generate angle steps\n", " angle_steps = []\n", " lower = (2 * np.pi / num_vertices) - irregularity\n", " upper = (2 * np.pi / num_vertices) + irregularity\n", " sum = 0\n", " for _ in range(num_vertices):\n", " tmp = random.uniform(lower, upper)\n", " angle_steps.append(tmp)\n", " sum += tmp\n", "\n", " # Normalize the steps so that the sum is 2*pi\n", " k = sum / (2 * np.pi)\n", " for i in range(num_vertices):\n", " angle_steps[i] /= k\n", "\n", " # Generate the points\n", " points = []\n", " angle = random.uniform(0, 2 * np.pi)\n", " for i in range(num_vertices):\n", " r_i = np.clip(random.gauss(avg_radius, spikiness), 0, 2 * avg_radius)\n", " x = center[0] + r_i * np.cos(angle)\n", " y = center[1] + r_i * np.sin(angle)\n", " points.append((x, y))\n", " angle += angle_steps[i]\n", "\n", " return points\n", "\n", "# Function to generate the tiling\n", "def generate_tiling(\n", " shape_types=['Circle', 'Square', 'Triangle', 'Polygon'],\n", " A_plane=100.0,\n", " A0=1.0,\n", " p=2.0,\n", " n_shapes=200,\n", " max_particle_size=5.0,\n", " random_orientation=True,\n", " polygon_sides=6,\n", " irregularity=0.0,\n", " spikiness=0.0\n", "):\n", " # Generate areas using Riemann zeta function power-law distribution\n", " # A_i = A0 / i^p, where the sum converges to A0 * zeta(p)\n", " zeta_p = zeta(p)\n", "\n", " # Generate areas for each shape: A_i = A0 / i^p\n", " areas = [A0] # First area (i=1)\n", " i = 2 # Start from i=2 since i=1 is already added\n", " while len(areas) < n_shapes:\n", " area = A0 / (i**p)\n", " if area <= 0:\n", " break\n", " size = np.sqrt(area / np.pi) if 'Circle' in shape_types else np.sqrt(area)\n", " if size > max_particle_size:\n", " i += 1\n", " continue\n", " areas.append(area)\n", " i += 1\n", " \n", " # Shuffle the areas to distribute sizes among shapes\n", " random.shuffle(areas)\n", " \n", " # Assign areas to shapes\n", " shape_assignments = []\n", " sizes_list = []\n", " for area in areas:\n", " shape = random.choice(shape_types)\n", " if shape == 'Circle':\n", " size = np.sqrt(area / np.pi) # Radius\n", " else:\n", " size = np.sqrt(area) # Side length for squares and triangles\n", " sizes_list.append(size)\n", " shape_assignments.append(shape)\n", " \n", " # Define the plane dimensions (square)\n", " L = np.sqrt(A_plane) # Length of the side of the square plane\n", " \n", " # Initialize list to store positions and sizes of the shapes\n", " positions = []\n", " \n", " # Function to check if a new shape overlaps with existing ones\n", " def is_overlapping(x_new, y_new, size_new, shape_new, positions):\n", " # Approximate the shape with a circle for overlap checking\n", " if shape_new == 'Circle':\n", " radius_new = size_new\n", " elif shape_new == 'Polygon':\n", " radius_new = size_new # Approximate radius for polygons\n", " else:\n", " radius_new = size_new * np.sqrt(2) / 2 # For squares and triangles\n", " for x, y, size, shape, angle, params in positions:\n", " if shape == 'Circle':\n", " radius = size\n", " elif shape == 'Polygon':\n", " radius = size\n", " else:\n", " radius = size * np.sqrt(2) / 2\n", " distance = np.hypot(x_new - x, y_new - y)\n", " if distance < (radius_new + radius):\n", " return True\n", " return False\n", " \n", " # Place the shapes randomly without overlapping\n", " for idx, (size, shape_type) in enumerate(zip(sizes_list, shape_assignments)):\n", " max_attempts = 1000\n", " for attempt in range(max_attempts):\n", " # Generate random position within the plane, ensuring the shape stays within bounds\n", " x = random.uniform(size, L - size)\n", " y = random.uniform(size, L - size)\n", " angle = random.uniform(0, 360) if random_orientation else 0\n", " # Check for overlap\n", " if not is_overlapping(x, y, size, shape_type, positions):\n", " # For polygons, store additional parameters\n", " params = {}\n", " if shape_type == 'Polygon':\n", " params['num_vertices'] = polygon_sides\n", " params['irregularity'] = irregularity\n", " params['spikiness'] = spikiness\n", " else:\n", " params = None\n", " positions.append((x, y, size, shape_type, angle, params))\n", " break\n", " else:\n", " print(f\"Could not place a shape of size {size} without overlap after {max_attempts} attempts.\")\n", " break\n", " \n", " # Plot the shapes\n", " fig, ax = plt.subplots(figsize=(8, 8))\n", " for x, y, size, shape_type, angle, params in positions:\n", " if shape_type == 'Circle':\n", " shape = plt.Circle((x, y), size, edgecolor='blue', facecolor='lightblue', alpha=0.6)\n", " shape.set_transform(\n", " plt.matplotlib.transforms.Affine2D().rotate_deg_around(x, y, angle) + ax.transData\n", " )\n", " ax.add_patch(shape)\n", " elif shape_type == 'Square':\n", " shape = patches.Rectangle(\n", " (x - size / 2, y - size / 2),\n", " size,\n", " size,\n", " angle=angle,\n", " edgecolor='green',\n", " facecolor='lightgreen',\n", " alpha=0.6\n", " )\n", " ax.add_patch(shape)\n", " elif shape_type == 'Triangle':\n", " # Equilateral triangle\n", " h = size * np.sqrt(3) / 2\n", " points = np.array([\n", " [x, y + 2 * h / 3],\n", " [x - size / 2, y - h / 3],\n", " [x + size / 2, y - h / 3]\n", " ])\n", " t = patches.Polygon(\n", " points,\n", " closed=True,\n", " edgecolor='red',\n", " facecolor='salmon',\n", " alpha=0.6\n", " )\n", " t.set_transform(\n", " plt.matplotlib.transforms.Affine2D().rotate_deg_around(x, y, angle) + ax.transData\n", " )\n", " ax.add_patch(t)\n", " elif shape_type == 'Polygon':\n", " num_vertices = params['num_vertices']\n", " irregularity = params['irregularity']\n", " spikiness = params['spikiness']\n", " avg_radius = size\n", " points = generate_polygon(\n", " center=(x, y),\n", " avg_radius=avg_radius,\n", " irregularity=irregularity,\n", " spikiness=spikiness,\n", " num_vertices=num_vertices\n", " )\n", " poly = patches.Polygon(\n", " points,\n", " closed=True,\n", " edgecolor='purple',\n", " facecolor='violet',\n", " alpha=0.6\n", " )\n", " ax.add_patch(poly)\n", " else:\n", " continue\n", " \n", " # Set plot parameters\n", " ax.set_xlim(0, L)\n", " ax.set_ylim(0, L)\n", " ax.set_aspect('equal', 'box')\n", " title_shapes = ', '.join(shape_types)\n", " ax.set_title(f'Iterative Tiling Using Riemann Zeta Function - {title_shapes}')\n", " ax.set_xlabel('X-axis')\n", " ax.set_ylabel('Y-axis')\n", " plt.grid(True)\n", " plt.show()\n", " \n", "# Interactive widget function\n", "def interactive_tiling(\n", " shape_types,\n", " A_plane,\n", " A0,\n", " p,\n", " n_shapes,\n", " max_particle_size,\n", " random_orientation,\n", " polygon_sides,\n", " irregularity,\n", " spikiness\n", "):\n", " generate_tiling(\n", " shape_types=list(shape_types),\n", " A_plane=A_plane,\n", " A0=A0,\n", " p=p,\n", " n_shapes=n_shapes,\n", " max_particle_size=max_particle_size,\n", " random_orientation=random_orientation,\n", " polygon_sides=polygon_sides,\n", " irregularity=irregularity,\n", " spikiness=spikiness\n", " )\n", "\n", "# Create interactive sliders and widgets\n", "interact(\n", " interactive_tiling,\n", " shape_types=SelectMultiple(\n", " options=['Circle', 'Square', 'Triangle', 'Polygon'],\n", " value=['Circle', 'Square', 'Triangle', 'Polygon'],\n", " description='Shape Types'\n", " ),\n", " A_plane=FloatSlider(value=100.0, min=50.0, max=500.0, step=10.0, description='Plane Area'),\n", " A0=FloatSlider(value=1.0, min=0.1, max=10.0, step=0.1, description='Initial Area A0'),\n", " p=FloatSlider(value=2.0, min=1.1, max=5.0, step=0.1, description='Exponent p'),\n", " n_shapes=IntSlider(value=200, min=50, max=1000, step=50, description='Number of Shapes'),\n", " max_particle_size=FloatSlider(value=5.0, min=0.5, max=20.0, step=0.5, description='Max Particle Size'),\n", " random_orientation=Checkbox(value=True, description='Random Orientation'),\n", " polygon_sides=IntSlider(value=6, min=3, max=12, step=1, description='Polygon Sides'),\n", " irregularity=FloatSlider(value=0.0, min=0.0, max=1.0, step=0.05, description='Irregularity'),\n", " spikiness=FloatSlider(value=0.0, min=0.0, max=1.0, step=0.05, description='Spikiness')\n", ")" ] }, { "cell_type": "code", "execution_count": 3, "id": "1feb1788-d05a-4fe1-9199-1085e2b8353c", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import matplotlib.patches as patches\n", "from scipy.special import zeta\n", "import random\n", "from ipywidgets import interact, FloatSlider, IntSlider, Dropdown, Checkbox\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "9529de2d-bf73-4376-9eeb-96684f101355", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "f261ff1215e24ff28fe24d803efa24fe", "version_major": 2, "version_minor": 0 }, "text/plain": [ "interactive(children=(Dropdown(description='Shape Type', options=('Circle', 'Square', 'Triangle'), value='Circ…" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\n", "# Function to generate the tiling\n", "def generate_tiling(\n", " shape_type='Circle',\n", " A_plane=100.0,\n", " A0=1.0,\n", " p=2.0,\n", " n_shapes=200,\n", " max_particle_size=5.0,\n", " random_orientation=True\n", "):\n", " # Generate areas using Riemann zeta function power-law distribution\n", " # A_i = A0 / i^p, where the sum converges to A0 * zeta(p)\n", " zeta_p = zeta(p)\n", "\n", " # Generate areas for each shape: A_i = A0 / i^p\n", " areas = [A0] # First area (i=1)\n", " i = 2 # Start from i=2 since i=1 is already added\n", " while len(areas) < n_shapes:\n", " area = A0 / (i**p)\n", " if area <= 0 or np.sqrt(area) > max_particle_size:\n", " i += 1\n", " continue\n", " areas.append(area)\n", " i += 1\n", "\n", " # Compute dimensions of the shapes\n", " if shape_type == 'Circle':\n", " sizes = [np.sqrt(area / np.pi) for area in areas] # Radii\n", " else:\n", " sizes = [np.sqrt(area) for area in areas] # Side lengths for squares and triangles\n", "\n", " # Define the plane dimensions (square)\n", " L = np.sqrt(A_plane) # Length of the side of the square plane\n", "\n", " # Initialize list to store positions and sizes of the shapes\n", " positions = []\n", "\n", " # Function to check if a new shape overlaps with existing ones\n", " def is_overlapping(x_new, y_new, size_new, positions):\n", " for x, y, size, angle in positions:\n", " if shape_type == 'Circle':\n", " distance = np.hypot(x_new - x, y_new - y)\n", " if distance < (size_new + size):\n", " return True\n", " else:\n", " # Approximate the shapes with circles for overlap checking\n", " distance = np.hypot(x_new - x, y_new - y)\n", " radius_new = size_new * np.sqrt(2) / 2\n", " radius = size * np.sqrt(2) / 2\n", " if distance < (radius_new + radius):\n", " return True\n", " return False\n", "\n", " # Place the shapes randomly without overlapping\n", " for size in sizes:\n", " max_attempts = 1000\n", " for attempt in range(max_attempts):\n", " # Generate random position within the plane, ensuring the shape stays within bounds\n", " x = random.uniform(size, L - size)\n", " y = random.uniform(size, L - size)\n", " angle = random.uniform(0, 360) if random_orientation else 0\n", " # Check for overlap\n", " if not is_overlapping(x, y, size, positions):\n", " positions.append((x, y, size, angle))\n", " break\n", " else:\n", " print(f\"Could not place a shape of size {size} without overlap after {max_attempts} attempts.\")\n", " break\n", "\n", " # Plot the shapes\n", " fig, ax = plt.subplots(figsize=(8, 8))\n", " for x, y, size, angle in positions:\n", " if shape_type == 'Circle':\n", " shape = plt.Circle((x, y), size, edgecolor='blue', facecolor='lightblue', alpha=0.6)\n", " elif shape_type == 'Square':\n", " shape = patches.Rectangle(\n", " (x - size / 2, y - size / 2),\n", " size,\n", " size,\n", " angle=angle,\n", " edgecolor='green',\n", " facecolor='lightgreen',\n", " alpha=0.6\n", " )\n", " elif shape_type == 'Triangle':\n", " # Equilateral triangle\n", " h = size * np.sqrt(3) / 2\n", " points = np.array([\n", " [x, y + 2 * h / 3],\n", " [x - size / 2, y - h / 3],\n", " [x + size / 2, y - h / 3]\n", " ])\n", " t = patches.Polygon(\n", " points,\n", " closed=True,\n", " edgecolor='red',\n", " facecolor='salmon',\n", " alpha=0.6\n", " )\n", " shape = t\n", " shape.set_transform(\n", " plt.matplotlib.transforms.Affine2D().rotate_deg_around(x, y, angle) + ax.transData\n", " )\n", " else:\n", " continue\n", "\n", " if shape_type in ['Circle', 'Square']:\n", " shape.set_transform(\n", " plt.matplotlib.transforms.Affine2D().rotate_deg_around(x, y, angle) + ax.transData\n", " )\n", " ax.add_patch(shape)\n", " elif shape_type == 'Triangle':\n", " ax.add_patch(shape)\n", "\n", " # Set plot parameters\n", " ax.set_xlim(0, L)\n", " ax.set_ylim(0, L)\n", " ax.set_aspect('equal', 'box')\n", " ax.set_title(f'Iterative Tiling Using Riemann Zeta Function - {shape_type}s')\n", " ax.set_xlabel('X-axis')\n", " ax.set_ylabel('Y-axis')\n", " plt.grid(True)\n", " plt.show()\n", "\n", "# Interactive widget function\n", "def interactive_tiling(\n", " shape_type,\n", " A_plane,\n", " A0,\n", " p,\n", " n_shapes,\n", " max_particle_size,\n", " random_orientation\n", "):\n", " generate_tiling(\n", " shape_type=shape_type,\n", " A_plane=A_plane,\n", " A0=A0,\n", " p=p,\n", " n_shapes=n_shapes,\n", " max_particle_size=max_particle_size,\n", " random_orientation=random_orientation\n", " )\n", "\n", "# Create interactive sliders and dropdowns\n", "interact(\n", " interactive_tiling,\n", " shape_type=Dropdown(options=['Circle', 'Square', 'Triangle'], value='Circle', description='Shape Type'),\n", " A_plane=FloatSlider(value=100.0, min=50.0, max=50000.0, step=100.0, description='Plane Area'),\n", " A0=FloatSlider(value=1.0, min=0.1, max=100.0, step=0.01, description='Initial Area A0'),\n", " p=FloatSlider(value=1.56, min=1.01, max=2.0, step=0.01, description='Exponent p'),\n", " n_shapes=IntSlider(value=20000, min=50, max=1000000, step=50, description='Number of Shapes'),\n", " max_particle_size=FloatSlider(value=5.0, min=0.5, max=20.0, step=0.5, description='Max Particle Size'),\n", " random_orientation=Checkbox(value=True, description='Random Orientation')\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "8fea6f32-90cb-4b4c-b503-90506b4896a5", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.11" } }, "nbformat": 4, "nbformat_minor": 5 }