Make cpp-mode parameters writable after construction (fix #350) - #352
Merged
Conversation
…ruction Fixes the silent no-op reported in issue toruseo#350: assigning Node.flow_capacity (and other parameters) after construction was only rebinding a Python attribute on the wrapper and never reached the C++ engine. - Add CppProperty descriptor: write-through attribute forwarding to the underlying C++ object, declared as one-liners on the wrapper classes - Add CppWorldProperty descriptor: same for CppWorld parameters that must also work before the C++ world is created (DUO_UPDATE_TIME, DUO_UPDATE_WEIGHT, DUO_NOISE) - Node: x, y, signal_offset, flow_capacity, flow_capacity_remain, number_of_lanes are now live read/write properties; setting flow_capacity alone also initializes flow_capacity_remain and derives number_of_lanes when unset, mirroring Python Node.__init__; unset values read as None as in Python mode - Link: u, kappa, tau, w, capacity, delta, delta_per_lane, merge_priority, number_of_lanes, signal_group, length are now live read/write properties; free_flow_speed/jam_density/q_star/k_star stay inert bookkeeping attributes exactly as in Python mode - Vehicle: orig/dest are now live read/write properties (accept Node or name string) - Remove write-only backing attributes _capacity_out_remain / _capacity_in_remain - Add 7 tests covering post-hoc assignment (identity with at-construction setting, cross-mode consistency with Python backend, and knob effectiveness) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- init_after_tmax_fix read length/u (now live C++ properties) inside a per-timestep list comprehension, causing ~780k C++ attribute accesses on a 360-link/1080-step scenario; hoist the free-flow travel time and use np.full - Cache Vehicle orig/dest on the Python side (the C++ engine never changes them itself); writes still go through to C++ Interleaved A/B benchmark (12 rounds, 1 thread) shows no measurable overhead vs the pre-property wrapper (median -2.6%, within VM noise), with bit-identical simulation results. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Python-side caches (Vehicle._orig/_dest, Link._length) could go silently stale if the C++ object is modified directly via the raw API or by future engine features, reintroducing the issue toruseo#350 failure mode in the read direction. Read live from C++ instead: - Vehicle.orig/dest resolve the wrapper node by integer id list index (same pattern as Vehicle.link), avoiding name-string conversion - Link.length becomes a plain CppProperty (now always reads as float) Profiling showed the caches had no measurable benefit; the earlier regression was the init_after_tmax_fix hot loop, which remains fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #350.
With the C++ backend, assigning
node.flow_capacity(or most other Node/Link/Vehicle/World parameters) after construction was silently ignored: the assignment only rebound a Python attribute on the wrapper object and never reached the C++ engine. This PR makes the wrapper parameters live read/write properties that forward to the C++ engine, so post-construction assignment behaves consistently with the Python backend.Design
Two small descriptors in
uxsim_cpp_wrapper.pyremove the need for per-attribute getter/setter boilerplate:CppProperty: one-line declaration of a write-through attribute forwarding to the underlying C++ object (e.g.u = CppProperty('vmax'))CppWorldProperty: same forCppWorldparameters that must also work before the C++ world is created (value lives in a Python backing attribute until then)Parameters with Python-mode-specific semantics remain explicit properties, so the special cases are visible at a glance.
Now readable/writable after construction
x,y,signal_offset,flow_capacity,flow_capacity_remain,number_of_lanes(in addition to existingsignal,signal_phase,signal_t)u,kappa,tau,w,capacity,delta,delta_per_lane,merge_priority,number_of_lanes,signal_group,length(in addition to existingcapacity_in/out(_remain))orig,dest(accept Node object or name string)DUO_UPDATE_TIME,DUO_UPDATE_WEIGHT,DUO_NOISE(read dynamically by the C++ engine, matching Python's runtime use)Python-mode compatibility details:
node.flow_capacity = valone is sufficient: it also (re)initializesflow_capacity_remainand derivesnumber_of_laneswhen unset, mirroringNode.__init__(as suggested in the issue)node.flow_capacity/node.number_of_lanesnow read asNoneas in Python mode (previously-1.0/0)free_flow_speed,jam_density,jam_density_per_lane,q_star,k_starremain inert bookkeeping attributes, exactly as in Python mode (useu/kappaorchange_free_flow_speed()/change_jam_density()to affect the simulation)No C++ side changes (all needed fields were already exposed via
def_rw).Tests
tests/test_cpp_mode.py: the issuecpp=True: assigningNode.flow_capacityafter construction is silently ignored (inconsistent with Python backend) #350 reproduction (post-hoc == at-construction, and cross-mode consistency with the Python backend), read/write round-trips for Node/Link parameters, and effectiveness checks for post-hocLink.u,Link.merge_priority,World.DUO_UPDATE_WEIGHT, andVehicle.destValidation
Benchmark
1 thread (
OMP_NUM_THREADS=1), 10x10 grid, ~9.5k platoons, 1080 timesteps, includes scenario build + simulation + basic analysis:init_after_tmax_fix(~780k property reads) was found by profiling and fixed in the second commit.🤖 Generated with Claude Code