Create, inspect, delete, and re-create SA/2D area connections in a real HEC-RAS
geometry file. This notebook demonstrates an engineering-oriented workflow for
the Dam connection in BaldEagleDamBrk.g13:
inspect the connection line, crest profile, gates, terrain, mesh, and boundary context
delete the Dam connection and prove the geometry no longer contains it
re-create the Dam connection from the original alignment with deliberately different hydraulic parameters
write a visibly different weir crest profile, including a raised spillway notch and lowered high shoulders
move the gate opening stations and raise the gate invert elevation
compute three scenarios: original connection, deleted/no-connection, and deleted-then-replaced connection
compare total flow time series from the original and replaced connection, with the no-connection result shown as an absent structure
Primary APIs used:
GeomLateral.get_connections() - list SA/2D connections
RasCmdr.compute_plan() - compute each scenario through HEC-RAS
HdfStruc and HdfResultsBreach - verify structure presence and extract total flow time series
HdfMesh and HdfBndry - read mesh cells, mesh perimeters, and BC lines from the geometry HDF
The terrain profile in this notebook is sampled directly from the extracted
example project's Terrain/Terrain50.dtm_20ft.tif raster. RasExamples.extract_project()
provides that terrain raster, the .rasmap, and the compiled geometry HDF used
for the map context.
Before modifying the connection, an engineer needs enough spatial and hydraulic
context to interpret the profile:
target connection alignment and adjacent storage/2D flow areas
2D mesh perimeter and local cells crossed by the connection
downstream boundary condition locations
underlying terrain along and around the connection
weir crest station/elevation profile and gate openings
The overview below locates the target connection in the model. The detailed
inspection maps that follow zoom to the connection line with a 25 percent margin
around its extents.
Python
target_coords=connection_lines[TARGET_CONNECTION]target_line=connection_line(target_coords)overview_bounds=expanded_bounds(mesh_areas.total_bounds,fraction=0.04,min_pad=1000.0)fig,ax=plt.subplots(figsize=(11,7))plot_context_map(ax,terrain_raster,mesh_areas,mesh_cells,bc_lines,overview_bounds,"Model Overview: Mesh, BC Lines, And SA/2D Connections",show_cells=False,)forname,coordsinconnection_lines.items():color="#b91c1c"ifname==TARGET_CONNECTIONelse"#7c2d12"linewidth=2.8ifname==TARGET_CONNECTIONelse1.4label=f"Target connection: {name}"ifname==TARGET_CONNECTIONelse"Other SA/2D connections"ax.plot(coords["X"],coords["Y"],color=color,linewidth=linewidth,alpha=0.95,zorder=8,label=label)mid=connection_line(coords).interpolate(0.5,normalized=True)ax.text(mid.x,mid.y,name,fontsize=8,ha="center",va="center",bbox=dict(facecolor="white",edgecolor=color,alpha=0.75,pad=1.5),zorder=16,)dedupe_legend(ax,loc="lower left")fig.tight_layout()save_figure(fig,"214_connection_authoring_model_overview.png")plt.show()
Compare HEC-RAS Results For Original, Deleted, And Replaced Connections¶
The file-level assertions above prove that the text geometry was deleted,
re-created, and round-tripped through GeomLateral. The hydraulic proof is
a three-scenario HEC-RAS comparison using Plan 04:
Original connection - unmodified Dam connection.
No Dam connection - Dam deleted from the geometry before compute.
Deleted + replaced connection - Dam deleted, re-created with the
modified profile and gate layout, then computed.
The no-connection scenario is expected to have no Dam structure group in
the HDF. The plot below shows that result as a zero line because there is no
structure through which to report Dam total flow.
Python
importshutilimportrefromras_commanderimportinit_ras_project,RasCmdrfromras_commander.hdfimportHdfResultsPlan,HdfResultsBreach,HdfStruclogging.getLogger("ras_commander").setLevel(logging.INFO)SCENARIO_ROOT=WORK_ROOT/"compute_scenarios"SCENARIO_ROOT.mkdir(parents=True,exist_ok=True)defcompute_errors(messages):lines=messages.splitlines()ifmessageselse[]return[lineforlineinlinesif"ERROR"inline.upper()and"VOLUME ACCOUNTING"notinline.upper()and"ITERATIONS"notinline.upper()and"WSEL ERROR"notinline.upper()]defupdate_program_versions(project_path,ras_version):forfilenamein[f"BaldEagleDamBrk.p{SCENARIO_PLAN_NUMBER}",GEOM_FILE_NAME,"BaldEagleDamBrk.u01",]:path=project_path/filenametext=path.read_text(encoding="utf-8",errors="replace")text=re.sub(r"Program Version=.*",f"Program Version={ras_version}",text)path.write_text(text,encoding="utf-8")defapply_no_connection(project_path):scenario_geom=project_path/GEOM_FILE_NAMEbefore=GeomLateral.get_connections(scenario_geom)GeomLateral.delete_connection(scenario_geom,TARGET_CONNECTION,create_backup=False)after=GeomLateral.get_connections(scenario_geom)assertTARGET_CONNECTIONnotinafter["Name"].valuesreturn{"Connections Before":len(before),"Connections After Delete":len(after),"Dam Present After Delete":False,}defapply_deleted_then_replaced(project_path):scenario_geom=project_path/GEOM_FILE_NAMEscenario_connections=GeomLateral.get_connections(scenario_geom)scenario_dam=scenario_connections.loc[scenario_connections["Name"]==TARGET_CONNECTION].iloc[0]scenario_coords=GeomLateral.get_connection_line_coords(scenario_geom,TARGET_CONNECTION)GeomLateral.delete_connection(scenario_geom,TARGET_CONNECTION,create_backup=False)after_delete_scenario=GeomLateral.get_connections(scenario_geom)assertTARGET_CONNECTIONnotinafter_delete_scenario["Name"].valuesGeomLateral.set_connection(scenario_geom,TARGET_CONNECTION,list(zip(scenario_coords["X"],scenario_coords["Y"])),upstream_area=scenario_dam["From"],downstream_area=scenario_dam["To"],routing_type=1,weir_width=REPLACEMENT_WEIR_WIDTH_FT,weir_coef=REPLACEMENT_WEIR_COEF,overflow_method_2d=True,create_backup=False,)GeomLateral.set_connection_profile(scenario_geom,TARGET_CONNECTION,replacement_profile,create_backup=False,)GeomLateral.set_connection_gates(scenario_geom,TARGET_CONNECTION,new_gates,create_backup=False,)after_replace_scenario=GeomLateral.get_connections(scenario_geom)profile=GeomLateral.get_connection_profile(scenario_geom,TARGET_CONNECTION)gates=GeomLateral.get_connection_gates(scenario_geom,TARGET_CONNECTION)assertTARGET_CONNECTIONinafter_replace_scenario["Name"].valuesassertnp.allclose(profile["Elevation"],replacement_profile["Elevation"],atol=0.001)assert[float(s)forsingates.iloc[0]["OpeningStations"]]==REPLACEMENT_GATE_STATIONS_FTreturn{"Connections Before":len(scenario_connections),"Connections After Delete":len(after_delete_scenario),"Connections After Replace":len(after_replace_scenario),"Profile Points":len(profile),"Gate Stations":gates.iloc[0]["OpeningStations"],"Gate Invert":float(gates.iloc[0]["InvertElevation"]),}defscenario_folder(suffix):returnSCENARIO_ROOT/f"{PROJECT_NAME}_{suffix}"defhdf_is_complete(hdf_path):ifnothdf_path.exists():returnFalsemessages=HdfResultsPlan.get_compute_messages(hdf_path)return"Complete Process"inmessagesandnotcompute_errors(messages)defscenario_geometry_is_ready(project_path,scenario_key):scenario_geom=project_path/GEOM_FILE_NAMEifnotscenario_geom.exists():returnFalsetry:scenario_connections=GeomLateral.get_connections(scenario_geom)dam_present=TARGET_CONNECTIONinscenario_connections["Name"].valuesifscenario_key=="original":returndam_presentifscenario_key=="deleted":returnnotdam_presentifscenario_key=="replaced":ifnotdam_present:returnFalseprofile=GeomLateral.get_connection_profile(scenario_geom,TARGET_CONNECTION)gates=GeomLateral.get_connection_gates(scenario_geom,TARGET_CONNECTION)return(len(profile)==len(replacement_profile)andnp.allclose(profile["Elevation"],replacement_profile["Elevation"],atol=0.001)and[float(s)forsingates.iloc[0]["OpeningStations"]]==REPLACEMENT_GATE_STATIONS_FTandnp.isclose(float(gates.iloc[0]["InvertElevation"]),REPLACEMENT_GATE_INVERT_FT))exceptException:returnFalsereturnFalsedefprepare_and_compute_scenario(label,scenario_key,suffix,modifier=None):project_path=scenario_folder(suffix)hdf_path=project_path/f"BaldEagleDamBrk.p{SCENARIO_PLAN_NUMBER}.hdf"ifhdf_is_complete(hdf_path)andscenario_geometry_is_ready(project_path,scenario_key):return{"Scenario":label,"Scenario Key":scenario_key,"Project Path":project_path,"HDF Path":hdf_path,"Compute Status":"reused complete HDF","Edit Summary":{},}ifproject_path.exists():shutil.rmtree(project_path)project_path=RasExamples.extract_project(PROJECT_NAME,output_path=SCENARIO_ROOT,suffix=suffix)ras_scenario=init_ras_project(project_path,"7.0")update_program_versions(project_path,ras_scenario.ras_version)edit_summary=modifier(project_path)ifmodifierelse{}result=RasCmdr.compute_plan(SCENARIO_PLAN_NUMBER,ras_object=ras_scenario,force_geompre=True,force_rerun=True,num_cores=4,verify=False,timeout_sec=900,)assertresult,f"HEC-RAS compute failed for {label}"asserthdf_path.exists(),f"Missing HDF after compute: {hdf_path}"messages=HdfResultsPlan.get_compute_messages(hdf_path)errors=compute_errors(messages)assertnoterrors,f"HEC-RAS reported errors for {label}: {errors[:5]}"return{"Scenario":label,"Scenario Key":scenario_key,"Project Path":project_path,"HDF Path":hdf_path,"Compute Status":"computed","Edit Summary":edit_summary,}scenario_specs=[("Original connection","original","214_original",None),("No Dam connection","deleted","214_no_connection",apply_no_connection),("Deleted + replaced connection","replaced","214_replaced",apply_deleted_then_replaced),]scenario_results=[prepare_and_compute_scenario(label,key,suffix,modifier)forlabel,key,suffix,modifierinscenario_specs]scenario_audit=pd.DataFrame([{"Scenario":item["Scenario"],"Compute Status":item["Compute Status"],"HDF":item["HDF Path"].name,"Project Folder":item["Project Path"].name,**item["Edit Summary"],}foriteminscenario_results])display(scenario_audit)flow_frames=[]flow_summary_rows=[]reference_datetimes=Noneforiteminscenario_results:hdf_path=item["HDF Path"]connections_in_hdf=HdfStruc.list_sa2d_connections(hdf_path)dam_in_hdf=TARGET_CONNECTIONinconnections_in_hdfifdam_in_hdf:ts=HdfResultsBreach.get_structure_variables(hdf_path,TARGET_CONNECTION).copy()ifreference_datetimesisNone:reference_datetimes=ts["datetime"].copy()else:assertreference_datetimesisnotNone,"Original scenario must be processed before no-connection scenario"ts=pd.DataFrame({"datetime":reference_datetimes,"total_flow":0.0,"weir_flow":0.0,"hw":np.nan,"tw":np.nan,})ts["Scenario"]=item["Scenario"]ts["Dam Present In HDF"]=dam_in_hdfflow_frames.append(ts)peak_idx=ts["total_flow"].abs().idxmax()flow_summary_rows.append({"Scenario":item["Scenario"],"Dam Present In HDF":dam_in_hdf,"Connections In HDF":connections_in_hdf,"Time Steps":len(ts),"Peak Abs Total Flow (cfs)":ts["total_flow"].abs().max(),"Peak Total Flow Time":ts.loc[peak_idx,"datetime"],"Peak Abs Weir Flow (cfs)":ts["weir_flow"].abs().max(),"Max Headwater (ft)":ts["hw"].max(),"Max Tailwater (ft)":ts["tw"].max(),})flow_comparison=pd.concat(flow_frames,ignore_index=True)flow_summary=pd.DataFrame(flow_summary_rows)display(flow_summary)original_ts=flow_comparison[flow_comparison["Scenario"]=="Original connection"]replaced_ts=flow_comparison[flow_comparison["Scenario"]=="Deleted + replaced connection"]total_flow_delta=original_ts[["datetime","total_flow"]].merge(replaced_ts[["datetime","total_flow"]],on="datetime",suffixes=("_original","_replaced"),)total_flow_delta["Replacement Minus Original (cfs)"]=(total_flow_delta["total_flow_replaced"]-total_flow_delta["total_flow_original"])max_abs_total_flow_difference=total_flow_delta["Replacement Minus Original (cfs)"].abs().max()peak_delta_time=total_flow_delta.loc[total_flow_delta["Replacement Minus Original (cfs)"].abs().idxmax(),"datetime",]fig,(ax,ax_delta)=plt.subplots(2,1,figsize=(12,7.5),sharex=True,gridspec_kw={"height_ratios":[3.0,1.25]},)line_styles={"Original connection":{"color":"#1f4e79","linewidth":2.1,"linestyle":"-"},"Deleted + replaced connection":{"color":"#b91c1c","linewidth":2.1,"linestyle":"-"},"No Dam connection":{"color":"0.35","linewidth":1.6,"linestyle":"--"},}forscenario_name,styleinline_styles.items():scenario_ts=flow_comparison[flow_comparison["Scenario"]==scenario_name]ax.plot(scenario_ts["datetime"],scenario_ts["total_flow"],label=scenario_name,**style,)ax.set_title(f"{TARGET_CONNECTION}: Total Flow Time Series By Connection Scenario")ax.set_ylabel("Total Flow (cfs)")ax.grid(True,alpha=0.25)ax.legend(loc="upper left")ax_delta.plot(total_flow_delta["datetime"],total_flow_delta["Replacement Minus Original (cfs)"],color="#7c2d12",linewidth=1.8,)ax_delta.axhline(0.0,color="0.25",linewidth=0.9)ax_delta.set_title(f"Replacement - Original total flow; max absolute difference "f"{max_abs_total_flow_difference:,.0f} cfs at {peak_delta_time}")ax_delta.set_ylabel("Delta (cfs)")ax_delta.set_xlabel("Simulation Time")ax_delta.grid(True,alpha=0.25)fig.autofmt_xdate()fig.tight_layout()save_figure(fig,"214_connection_authoring_total_flow_comparison.png")plt.show()assertflow_summary.loc[flow_summary["Scenario"]=="Original connection","Dam Present In HDF",].iloc[0]assertnotflow_summary.loc[flow_summary["Scenario"]=="No Dam connection","Dam Present In HDF",].iloc[0]assertflow_summary.loc[flow_summary["Scenario"]=="Deleted + replaced connection","Dam Present In HDF",].iloc[0]assertmax_abs_total_flow_difference>1000.0print(f"Scenario comparison complete. Max absolute replacement/original total-flow difference: {max_abs_total_flow_difference:,.0f} cfs.")
The HDF comparison above proves three separate outcomes:
the original Dam connection produces a Dam total-flow time series
the deleted/no-connection result has no Dam structure in the HDF
the deleted-then-replaced Dam connection computes and produces a different Dam total-flow time series
Python
forscenario_namein["Original connection","Deleted + replaced connection","No Dam connection",]:scenario_ts=flow_comparison[flow_comparison["Scenario"]==scenario_name]print(f"\n{scenario_name}: first 5 time steps")display(scenario_ts[["datetime","total_flow","weir_flow","hw","tw","Dam Present In HDF"]].head())print(f"{scenario_name}: last 5 time steps")display(scenario_ts[["datetime","total_flow","weir_flow","hw","tw","Dam Present In HDF"]].tail())no_connection_peak=flow_summary.loc[flow_summary["Scenario"]=="No Dam connection","Peak Abs Total Flow (cfs)",].iloc[0]replaced_peak=flow_summary.loc[flow_summary["Scenario"]=="Deleted + replaced connection","Peak Abs Total Flow (cfs)",].iloc[0]assertno_connection_peak==0.0assertreplaced_peak>0.0print(f"\n{'='*70}")print("STRUCTURE RESULT VALIDATION")print(f"{'='*70}")print(" Original: Dam present with total-flow time series")print(" No Dam connection: Dam absent from HDF; plotted as zero structure flow")print(" Deleted + replaced: Dam present with modified total-flow time series")print(f" Max flow difference: {max_abs_total_flow_difference:,.0f} cfs")print(f"{'='*70}")
Text Only
Original connection: first 5 time steps
datetime
total_flow
weir_flow
hw
tw
Dam Present In HDF
0
1999-01-01 12:00:00
1130.885986
0.0
650.001099
586.846252
True
1
1999-01-01 12:05:00
1178.005005
0.0
650.000977
586.988403
True
2
1999-01-01 12:10:00
1225.125977
0.0
650.001038
587.074036
True
3
1999-01-01 12:15:00
1272.250000
0.0
650.001404
587.155518
True
4
1999-01-01 12:20:00
1319.377808
0.0
650.002075
587.235779
True
Text Only
Original connection: last 5 time steps
datetime
total_flow
weir_flow
hw
tw
Dam Present In HDF
860
1999-01-04 11:40:00
67404.289062
64843.703125
666.902344
596.484436
True
861
1999-01-04 11:45:00
67296.960938
64736.550781
666.892090
596.475769
True
862
1999-01-04 11:50:00
67189.703125
64629.457031
666.881836
596.467041
True
863
1999-01-04 11:55:00
67082.515625
64522.445312
666.871582
596.458252
True
864
1999-01-04 12:00:00
66974.757812
64414.859375
666.861267
596.449524
True
Text Only
Deleted + replaced connection: first 5 time steps
datetime
total_flow
weir_flow
hw
tw
Dam Present In HDF
1730
1999-01-01 12:00:00
1960.314941
0.0
649.999817
588.006348
True
1731
1999-01-01 12:05:00
2041.949707
0.0
649.997620
588.230042
True
1732
1999-01-01 12:10:00
2123.585205
0.0
649.995605
588.312561
True
1733
1999-01-01 12:15:00
2205.222412
0.0
649.993835
588.462280
True
1734
1999-01-01 12:20:00
2286.859619
0.0
649.992188
588.328613
True
Text Only
Deleted + replaced connection: last 5 time steps
datetime
total_flow
weir_flow
hw
tw
Dam Present In HDF
2590
1999-01-04 11:40:00
66772.914062
62122.234375
670.354126
596.580994
True
2591
1999-01-04 11:45:00
66664.257812
62013.902344
670.344360
596.572449
True
2592
1999-01-04 11:50:00
66555.687500
61905.652344
670.334595
596.563904
True
2593
1999-01-04 11:55:00
66447.875000
61798.164062
670.324890
596.555420
True
2594
1999-01-04 12:00:00
66339.476562
61690.089844
670.315125
596.546936
True
Text Only
No Dam connection: first 5 time steps
datetime
total_flow
weir_flow
hw
tw
Dam Present In HDF
865
1999-01-01 12:00:00
0.0
0.0
NaN
NaN
False
866
1999-01-01 12:05:00
0.0
0.0
NaN
NaN
False
867
1999-01-01 12:10:00
0.0
0.0
NaN
NaN
False
868
1999-01-01 12:15:00
0.0
0.0
NaN
NaN
False
869
1999-01-01 12:20:00
0.0
0.0
NaN
NaN
False
Text Only
No Dam connection: last 5 time steps
datetime
total_flow
weir_flow
hw
tw
Dam Present In HDF
1725
1999-01-04 11:40:00
0.0
0.0
NaN
NaN
False
1726
1999-01-04 11:45:00
0.0
0.0
NaN
NaN
False
1727
1999-01-04 11:50:00
0.0
0.0
NaN
NaN
False
1728
1999-01-04 11:55:00
0.0
0.0
NaN
NaN
False
1729
1999-01-04 12:00:00
0.0
0.0
NaN
NaN
False
Text Only
======================================================================
STRUCTURE RESULT VALIDATION
======================================================================
Original: Dam present with total-flow time series
No Dam connection: Dam absent from HDF; plotted as zero structure flow
Deleted + replaced: Dam present with modified total-flow time series
Max flow difference: 18,889 cfs
======================================================================