Skip to content

Commit bfee2ce

Browse files
committed
Add simulation_base file and updated examples
1 parent 1818d4d commit bfee2ce

File tree

5 files changed

+438
-534
lines changed

5 files changed

+438
-534
lines changed

examples/Envy/Envy_simulation.py

Lines changed: 57 additions & 261 deletions
Original file line numberDiff line numberDiff line change
@@ -1,294 +1,90 @@
1+
import sys
2+
sys.path.append('../../')
13
from dataclasses import dataclass
24
import numpy as np
3-
from helper import cut_from
5+
from simulation_base import SimulationConfig, TreeSimulationBase
46

57
@dataclass
6-
class EnvySimulationConfig:
8+
class EnvySimulationConfig(SimulationConfig):
79
"""Configuration for Envy trellis tree simulation parameters."""
810

9-
# Tying and Pruning
11+
# Override base defaults for Envy-specific values
1012
num_iteration_tie: int = 5
1113
num_iteration_prune: int = 16
14+
pruning_age_threshold: int = 6
15+
derivation_length: int = 128
1216

13-
# Display
14-
label: bool = True
15-
16-
# Support Structure
17+
# Envy-specific Support Structure
1718
support_trunk_wire_point = None
1819
support_num_wires: int = 14
19-
support_spacing_wires: int = 1
2020

21-
# Point Generation
21+
# Envy-specific Point Generation (V-trellis)
2222
trellis_x_value: float = 0.45
2323
trellis_z_start: float = 0.6
2424
trellis_z_end: float = 3.4
2525
trellis_z_spacing: float = 0.45
2626

27-
# Energy and Tying Parameters
28-
energy_threshold: float = 0.25
29-
energy_decay_factor: float = 105
30-
tolerance: float = 1e-5
31-
# Energy and Tying Parameters
32-
energy_distance_weight: float = 0.5 # Weight for distance in energy calculation (was hardcoded /2)
33-
energy_threshold: float = 1.0 # Maximum energy threshold for tying
34-
35-
36-
# Pruning Parameters
37-
pruning_age_threshold: int = 6
38-
39-
# L-System Parameters
40-
derivation_length: int = 128
41-
42-
# Growth Parameters
27+
# Envy-specific Growth Parameters
4328
growth_length: float = 0.1
4429
bud_spacing_age: int = 2
4530

46-
# Visualization Parameters
47-
attractor_point_width: int = 10
48-
49-
50-
def generate_points_v_trellis(simulation_config):
51-
"""
52-
Generate 3D points for the V-trellis wire structure.
5331

54-
Creates a linear array of wire attachment points along the x-axis at a fixed
55-
height (z) and depth (y). The points are spaced evenly within the configured
56-
z-range and used to construct the trellis support structure.
32+
class EnvySimulation(TreeSimulationBase):
5733
"""
58-
x = np.full((7,), simulation_config.trellis_x_value).astype(float)
59-
y = np.full((7,), 0).astype(float)
60-
z = np.arange(simulation_config.trellis_z_start,
61-
simulation_config.trellis_z_end,
62-
simulation_config.trellis_z_spacing)
63-
64-
pts = []
65-
for i in range(x.shape[0]):
66-
pts.append((-x[i], y[i], z[i]))
67-
pts.append((x[i], y[i], z[i]))
68-
return pts
69-
70-
71-
72-
def get_energy_mat(branches, arch, simulation_config):
73-
"""
74-
Calculate the energy matrix for optimal branch-to-wire assignment.
75-
76-
This function computes an energy cost matrix where each entry represents the
77-
"cost" of assigning a specific branch to a specific wire in the trellis system.
78-
The energy is based on the Euclidean distance from wire attachment points to
79-
both the start and end points of each branch, weighted by the simulation's
80-
distance weight parameter.
34+
Envy trellis architecture simulation.
8135
82-
The algorithm uses a greedy optimization approach where branches are assigned
83-
to the lowest-energy available wire that hasn't reached capacity.
84-
85-
Args:
86-
branches: List of branch objects to be assigned to wires
87-
arch: Support architecture object containing wire information
88-
89-
Returns:
90-
numpy.ndarray: Energy matrix of shape (num_branches, num_wires) where
91-
matrix[i][j] is the energy cost of assigning branch i to wire j.
92-
Untied branches and occupied wires have infinite energy (np.inf).
36+
Implements the Envy V-trellis training system with wires arranged in a V-shape
37+
on both sides of the tree row.
9338
"""
94-
num_branches = len(branches)
95-
num_wires = len(arch.branch_supports)
96-
97-
# Initialize energy matrix with infinite values (impossible assignments)
98-
energy_matrix = np.full((num_branches, num_wires), np.inf)
99-
100-
# Calculate energy costs for all valid branch-wire combinations
101-
for branch_idx, branch in enumerate(branches):
102-
# Skip branches that are already tied
103-
if branch.tying.has_tied:
104-
continue
105-
106-
for wire_id, wire in arch.branch_supports.items():
107-
# Skip wires that already have a branch attached
108-
if wire.num_branch >= 1:
109-
continue
110-
111-
# Calculate weighted distance energy for this branch-wire pair
112-
# Energy considers distance from wire to both branch endpoints
113-
wire_point = np.array(wire.point)
114-
branch_start = np.array(branch.location.start)
115-
branch_end = np.array(branch.location.end)
116-
117-
start_distance_energy = np.sum((wire_point - branch_start) ** 2)
118-
end_distance_energy = np.sum((wire_point - branch_end) ** 2)
119-
120-
total_energy = (start_distance_energy + end_distance_energy) * simulation_config.energy_distance_weight
121-
122-
energy_matrix[branch_idx, wire_id] = total_energy
12339

124-
return energy_matrix
40+
def generate_points(self):
41+
"""
42+
Generate 3D points for the V-trellis wire structure.
12543
126-
def decide_guide(energy_matrix, branches, arch, simulation_config):
127-
"""
128-
Perform greedy assignment of branches to wires based on energy matrix.
129-
130-
This function implements a greedy optimization algorithm that iteratively assigns
131-
the branch-wire pair with the lowest energy cost. Once a branch is assigned to
132-
a wire, both that branch and wire are marked as unavailable (infinite energy)
133-
to prevent further assignments.
134-
135-
The algorithm continues until no valid assignments remain (all remaining energies
136-
are infinite or above the threshold).
137-
138-
Args:
139-
energy_matrix: numpy.ndarray of shape (num_branches, num_wires) with energy costs
140-
branches: List of branch objects to be assigned
141-
arch: Support architecture containing wire information
44+
Creates a linear array of wire attachment points along the x-axis at a fixed
45+
height (z) and depth (y). The points are spaced evenly within the configured
46+
z-range and used to construct the trellis support structure.
14247
143-
Returns:
144-
None: Modifies branches and arch in-place with new assignments
145-
"""
146-
num_branches, num_wires = energy_matrix.shape
147-
148-
# Early return if no branches or wires to assign
149-
if num_branches == 0 or num_wires == 0:
150-
return
151-
152-
# Continue making assignments until no valid ones remain
153-
while True:
154-
# Find the minimum energy value and its position
155-
min_energy_indices = np.argwhere(energy_matrix == np.min(energy_matrix))
156-
157-
# If no valid indices found or matrix is empty, stop
158-
if len(min_energy_indices) == 0:
159-
break
160-
161-
# Get the first (and typically only) minimum energy position
162-
branch_idx, wire_id = min_energy_indices[0]
163-
min_energy = energy_matrix[branch_idx, wire_id]
164-
165-
# Stop if minimum energy is infinite (no valid assignments) or above threshold
166-
if np.isinf(min_energy) or min_energy > simulation_config.energy_threshold:
167-
break
168-
169-
# Get the branch and wire objects
170-
branch = branches[branch_idx]
171-
wire = arch.branch_supports[wire_id]
172-
173-
# Skip if branch is already tied (defensive check)
174-
if branch.tying.has_tied:
175-
# Mark this assignment as invalid and continue
176-
energy_matrix[branch_idx, wire_id] = np.inf
177-
continue
178-
179-
# Perform the assignment
180-
branch.tying.guide_target = wire
181-
wire.add_branch()
182-
183-
# Mark branch and wire as unavailable for future assignments
184-
# Set entire row (branch) to infinity - this branch can't be assigned again
185-
energy_matrix[branch_idx, :] = np.inf
186-
# Set entire column (wire) to infinity - this wire can't accept more branches
187-
energy_matrix[:, wire_id] = np.inf
188-
189-
190-
def prune(lstring, simulation_config):
191-
"""
192-
Prune old branches that exceed the age threshold and haven't been tied to wires.
193-
194-
This function implements the pruning strategy for the tree training simulation.
195-
It identifies branches that have grown too old (exceeding the pruning age threshold)
196-
but haven't been successfully tied to trellis wires. Such branches are considered
197-
unproductive and are removed from the L-System to encourage new growth.
198-
199-
The pruning criteria are:
200-
1. Branch age exceeds the configured pruning threshold
201-
2. Branch has not been tied to any trellis wire
202-
3. Branch has not already been marked for cutting
203-
204-
When a branch meets all criteria, it is:
205-
- Marked as cut (to prevent re-processing)
206-
- Removed from the L-System string using cut_from()
207-
208-
Args:
209-
lstring: The current L-System string containing modules and their parameters
210-
211-
Returns:
212-
bool: True if a branch was pruned, False if no eligible branches found
213-
214-
Note:
215-
This function processes one branch at a time and returns immediately after
216-
pruning a single branch. It should be called repeatedly (e.g., in a while loop)
217-
until no more pruning operations are possible. The cut_from() function handles
218-
the actual removal of the branch and any dependent substructures from the string.
219-
"""
220-
for position, symbol in enumerate(lstring):
221-
# Check if this is a WoodStart module (represents a branch)
222-
if symbol.name == 'WoodStart':
223-
branch = symbol[0].type
224-
225-
# Check pruning criteria
226-
age_exceeds_threshold = branch.info.age > simulation_config.pruning_age_threshold
227-
not_tied_to_wire = not branch.tying.has_tied
228-
not_already_cut = not branch.info.cut
229-
is_prunable = branch.info.prunable
230-
231-
# Prune if all criteria are met
232-
if age_exceeds_threshold and not_tied_to_wire and not_already_cut and is_prunable:
233-
# Mark branch as cut to prevent re-processing
234-
branch.info.cut = True
48+
Returns:
49+
list: List of (x, y, z) tuples representing wire attachment points in V-trellis formation
50+
"""
51+
x = np.full((7,), self.config.trellis_x_value).astype(float)
52+
y = np.full((7,), 0).astype(float)
53+
z = np.arange(self.config.trellis_z_start,
54+
self.config.trellis_z_end,
55+
self.config.trellis_z_spacing)
56+
57+
pts = []
58+
for i in range(x.shape[0]):
59+
pts.append((-x[i], y[i], z[i]))
60+
pts.append((x[i], y[i], z[i]))
61+
return pts
62+
63+
64+
# Backwards compatibility: provide standalone functions that use the class
65+
def generate_points_v_trellis(simulation_config):
66+
"""Backward compatibility wrapper for generate_points."""
67+
sim = EnvySimulation(simulation_config)
68+
return sim.generate_points()
23569

236-
# Remove the branch from the L-System string
237-
print("Pruning branch at position:", position, lstring[position]) # Debug statement
238-
lstring = cut_from(position, lstring)
70+
def get_energy_mat(branches, arch, simulation_config):
71+
"""Backward compatibility wrapper for get_energy_mat."""
72+
sim = EnvySimulation(simulation_config)
73+
return sim.get_energy_mat(branches, arch)
23974

240-
return True
75+
def decide_guide(energy_matrix, branches, arch, simulation_config):
76+
"""Backward compatibility wrapper for decide_guide."""
77+
sim = EnvySimulation(simulation_config)
78+
return sim.decide_guide(energy_matrix, branches, arch)
24179

242-
return False
80+
def prune(lstring, simulation_config):
81+
"""Backward compatibility wrapper for prune."""
82+
sim = EnvySimulation(simulation_config)
83+
return sim.prune(lstring)
24384

24485
def tie(lstring, simulation_config):
245-
"""
246-
Perform tying operation on eligible branches in the L-System string.
247-
248-
This function searches through the L-System string for 'WoodStart' modules that
249-
represent branches ready for tying to trellis wires. It identifies branches that:
250-
1. Have tying properties (tying attribute exists)
251-
2. Have a defined tie axis (tie_axis is not None)
252-
3. Have not been tied yet (tie_updated is False)
253-
4. Have guide points available for wire attachment
254-
255-
When an eligible branch is found, it performs the tying operation by:
256-
- Marking the branch as tied (tie_updated = False)
257-
- Adding the branch to the target wire
258-
- Calling the branch's tie_lstring method to modify the L-System string
259-
260-
Args:
261-
lstring: The current L-System string containing modules and their parameters
262-
263-
Returns:
264-
bool: True if a tying operation was performed, False if no eligible branches found
265-
266-
Note:
267-
This function processes one branch at a time and returns immediately after
268-
tying a single branch. It should be called repeatedly (e.g., in a while loop)
269-
until no more tying operations are possible.
270-
"""
271-
for position, symbol in enumerate(lstring):
272-
# Check if this is a WoodStart module with tying capabilities
273-
if (symbol == 'WoodStart' and
274-
hasattr(symbol[0].type, 'tying') and
275-
getattr(symbol[0].type.tying, 'tie_axis', None) is not None):
276-
277-
branch = symbol[0].type
278-
279-
# Skip branches that have already been processed for tying
280-
if not branch.tying.tie_updated:
281-
continue
282-
283-
# Check if branch has guide points for wire attachment
284-
if branch.tying.guide_points:
285-
# Perform the tying operation
286-
branch.tying.tie_updated = False
287-
branch.tying.guide_target.add_branch()
288-
289-
# Update the L-System string with tying modifications
290-
lstring, modifications_count = branch.tie_lstring(lstring, position)
291-
292-
return True
86+
"""Backward compatibility wrapper for tie."""
87+
sim = EnvySimulation(simulation_config)
88+
return sim.tie(lstring)
89+
29390

294-
return False

examples/UFO/UFO.lpy

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,13 @@ grow_object(plant_segment) :
202202
for module in plant_segment.post_bud_rule(plant_segment, simulation_config):
203203
nproduce new(module[0], *module[1])
204204

205-
if should_bud(plant_segment, simulation_config):
206-
# Age-based bud production for lateral branching
207-
nproduce bud(ParameterSet(type = plant_segment, num_buds = 0))
208-
209-
if plant_segment.post_bud_rule(plant_segment, simulation_config):
210-
for module in plant_segment.post_bud_rule(plant_segment, simulation_config):
211-
nproduce new(module[0], *module[1])
205+
if should_bud(plant_segment, simulation_config):
206+
# Age-based bud production for lateral branching
207+
nproduce bud(ParameterSet(type = plant_segment, num_buds = 0))
208+
209+
if plant_segment.post_bud_rule(plant_segment, simulation_config):
210+
for module in plant_segment.post_bud_rule(plant_segment, simulation_config):
211+
nproduce new(module[0], *module[1])
212212

213213
produce grow_object(plant_segment)
214214

examples/UFO/UFO_prototypes.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def post_bud_rule(self, plant_segment, simulation_config ):
120120
growth_length=0.05,
121121
thickness_increment=0.,
122122
color=[0, 255, 0],
123-
bud_spacing_age=2, # Spurs bud every 1 age unit
123+
bud_spacing_age=1, # Spurs bud every 1 age unit
124124
curve_x_range=(-0.2, 0.2), # Tighter bounds for spur curves
125125
curve_y_range=(-0.2, 0.2), # Tighter bounds for spur curves
126126
curve_z_range=(-1, 1) # Same Z range
@@ -151,7 +151,8 @@ def post_bud_rule(self, plant_segment, simulation_config ):
151151
bud_spacing_age=2, # Trunk buds every 4 age units
152152
curve_x_range=(-0.3, 0.3), # Conservative bounds for trunk
153153
curve_y_range=(-0.3, 0.3), # Conservative bounds for trunk
154-
curve_z_range=(-0.5, 0.5) # Tighter Z range for trunk
154+
curve_z_range=(-0.5, 0.5), # Tighter Z range for trunk
155+
prunable=False
155156
)
156157

157158
branch_config = BasicWoodConfig(

0 commit comments

Comments
 (0)