Skip to content

Commit 6ef13ca

Browse files
fix(): use params entity registry for update_persistent_entities (#1898)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9392f2c commit 6ef13ca

3 files changed

Lines changed: 64 additions & 4 deletions

File tree

flow360/component/project_utils.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
)
1616
from flow360.component.simulation.framework.base_model import Flow360BaseModel
1717
from flow360.component.simulation.framework.entity_base import EntityList
18+
from flow360.component.simulation.framework.entity_registry import EntityRegistry
1819
from flow360.component.simulation.framework.param_utils import AssetCache
1920
from flow360.component.simulation.outputs.outputs import (
2021
SurfaceIntegralOutput,
@@ -498,6 +499,31 @@ def _set_up_default_reference_geometry(params: SimulationParams, length_unit: Le
498499
return params
499500

500501

502+
def _build_deduplicated_entity_registry_from_params(params: SimulationParams) -> EntityRegistry:
503+
"""
504+
Build a deduplicated entity registry from params' stored entities.
505+
506+
params.used_entity_registry may contain duplicates (same entity used in multiple
507+
models/outputs). This function deduplicates by (entity_type, identifier), where
508+
identifier is private_attribute_id when available, falling back to entity name
509+
to avoid collapsing distinct entities that share a None id.
510+
"""
511+
registry = EntityRegistry()
512+
seen_keys = set()
513+
for entity_type, entities in params.used_entity_registry.internal_registry.items():
514+
for entity in entities:
515+
identifier = (
516+
entity.private_attribute_id
517+
if entity.private_attribute_id is not None
518+
else entity.name
519+
)
520+
key = (entity_type, identifier)
521+
if key not in seen_keys:
522+
registry.register(entity)
523+
seen_keys.add(key)
524+
return registry
525+
526+
501527
def set_up_params_for_uploading( # pylint: disable=too-many-arguments
502528
root_asset,
503529
length_unit: LengthType,
@@ -557,12 +583,13 @@ def set_up_params_for_uploading( # pylint: disable=too-many-arguments
557583
# Legacy workflow (without DraftContext): use root_asset.entity_info
558584
# User may have made modifications to the entities which is recorded in asset's entity registry
559585
# We need to reflect these changes.
560-
root_asset.entity_info.update_persistent_entities(
561-
asset_entity_registry=root_asset.internal_registry
586+
entity_info = root_asset.entity_info
587+
entity_info.update_persistent_entities(
588+
asset_entity_registry=_build_deduplicated_entity_registry_from_params(params)
562589
)
563590

564591
# Check if there are any new draft entities that have been added in the params by the user
565-
entity_info = _set_up_params_non_persistent_entity_info(root_asset.entity_info, params)
592+
entity_info = _set_up_params_non_persistent_entity_info(entity_info, params)
566593

567594
# If the customer just load the param without re-specify the same set of entity grouping tags,
568595
# we need to update the entity grouping tags to the ones in the SimulationParams.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"data": {
3-
"simulationJson": "{\"version\": \"25.2.1\", \"unit_system\": {\"name\": \"SI\"}, \"reference_geometry\": {\"moment_center\": {\"value\": [0.0, 0.0, 0.0], \"units\": \"m\"}, \"moment_length\": {\"value\": [1.0, 1.0, 1.0], \"units\": \"m\"}, \"area\": {\"value\": 1.0, \"units\": \"m**2\"}}, \"operating_condition\": {\"type_name\": \"AerospaceCondition\", \"private_attribute_constructor\": \"default\", \"private_attribute_input_cache\": {\"alpha\": {\"value\": 0.0, \"units\": \"degree\"}, \"beta\": {\"value\": 0.0, \"units\": \"degree\"}, \"thermal_state\": {\"type_name\": \"ThermalState\", \"private_attribute_constructor\": \"default\", \"private_attribute_input_cache\": {}, \"temperature\": {\"value\": 288.15, \"units\": \"K\"}, \"density\": {\"value\": 1.225, \"units\": \"kg/m**3\"}, \"material\": {\"type\": \"air\", \"name\": \"air\", \"dynamic_viscosity\": {\"reference_viscosity\": {\"value\": 1.716e-05, \"units\": \"Pa*s\"}, \"reference_temperature\": {\"value\": 273.15, \"units\": \"K\"}, \"effective_temperature\": {\"value\": 110.4, \"units\": \"K\"}}}}}, \"alpha\": {\"value\": 0.0, \"units\": \"degree\"}, \"beta\": {\"value\": 0.0, \"units\": \"degree\"}, \"thermal_state\": {\"type_name\": \"ThermalState\", \"private_attribute_constructor\": \"default\", \"private_attribute_input_cache\": {}, \"temperature\": {\"value\": 288.15, \"units\": \"K\"}, \"density\": {\"value\": 1.225, \"units\": \"kg/m**3\"}, \"material\": {\"type\": \"air\", \"name\": \"air\", \"dynamic_viscosity\": {\"reference_viscosity\": {\"value\": 1.716e-05, \"units\": \"Pa*s\"}, \"reference_temperature\": {\"value\": 273.15, \"units\": \"K\"}, \"effective_temperature\": {\"value\": 110.4, \"units\": \"K\"}}}}}, \"models\": [{\"type\": \"Wall\", \"entities\": {\"stored_entities\": []}, \"name\": \"Wall\", \"use_wall_function\": false, \"heat_spec\": {\"value\": {\"value\": 0.0, \"units\": \"W/m**2\"}, \"type_name\": \"HeatFlux\"}, \"roughness_height\": {\"value\": 0.0, \"units\": \"m\"}}, {\"type\": \"Freestream\", \"entities\": {\"stored_entities\": []}, \"name\": \"Freestream\"}, {\"material\": {\"type\": \"air\", \"name\": \"air\", \"dynamic_viscosity\": {\"reference_viscosity\": {\"value\": 1.716e-05, \"units\": \"Pa*s\"}, \"reference_temperature\": {\"value\": 273.15, \"units\": \"K\"}, \"effective_temperature\": {\"value\": 110.4, \"units\": \"K\"}}}, \"initial_condition\": {\"type_name\": \"NavierStokesInitialCondition\", \"rho\": \"rho\", \"u\": \"u\", \"v\": \"v\", \"w\": \"w\", \"p\": \"p\"}, \"type\": \"Fluid\", \"navier_stokes_solver\": {\"absolute_tolerance\": 1e-10, \"relative_tolerance\": 0.0, \"order_of_accuracy\": 2, \"equation_evaluation_frequency\": 1, \"linear_solver\": {\"max_iterations\": 30}, \"CFL_multiplier\": 1.0, \"kappa_MUSCL\": -1.0, \"numerical_dissipation_factor\": 1.0, \"limit_velocity\": false, \"limit_pressure_density\": false, \"type_name\": \"Compressible\", \"low_mach_preconditioner\": false, \"update_jacobian_frequency\": 4, \"max_force_jac_update_physical_steps\": 0}, \"turbulence_model_solver\": {\"absolute_tolerance\": 1e-08, \"relative_tolerance\": 0.0, \"order_of_accuracy\": 2, \"equation_evaluation_frequency\": 4, \"linear_solver\": {\"max_iterations\": 20}, \"CFL_multiplier\": 2.0, \"type_name\": \"SpalartAllmaras\", \"reconstruction_gradient_limiter\": 0.5, \"quadratic_constitutive_relation\": false, \"modeling_constants\": {\"type_name\": \"SpalartAllmarasConsts\", \"C_DES\": 0.72, \"C_d\": 8.0, \"C_cb1\": 0.1355, \"C_cb2\": 0.622, \"C_sigma\": 0.6666666666666666, \"C_v1\": 7.1, \"C_vonKarman\": 0.41, \"C_w2\": 0.3, \"C_t3\": 1.2, \"C_t4\": 0.5, \"C_min_rd\": 10.0}, \"update_jacobian_frequency\": 4, \"max_force_jac_update_physical_steps\": 0, \"rotation_correction\": false}, \"transition_model_solver\": {\"type_name\": \"None\"}}], \"time_stepping\": {\"type_name\": \"Steady\", \"max_steps\": 2000, \"CFL\": {\"type\": \"adaptive\", \"min\": 0.1, \"max\": 10000.0, \"max_relative_change\": 1.0, \"convergence_limiting_factor\": 0.25}}, \"user_defined_fields\": [], \"outputs\": [{\"frequency\": -1, \"frequency_offset\": 0, \"output_format\": \"paraview\", \"name\": \"Surface output\", \"entities\": {\"stored_entities\": [{\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"name\": \"*\", \"private_attribute_sub_components\": []}]}, \"write_single_file\": false, \"output_fields\": {\"items\": [\"Cp\", \"yPlus\", \"Cf\", \"CfVec\"]}, \"output_type\": \"SurfaceOutput\"}], \"private_attribute_asset_cache\": {\"project_length_unit\": {\"value\": 1.0, \"units\": \"m\"}, \"project_entity_info\": {\"type_name\": \"VolumeMeshEntityInfo\", \"zones\": [{\"private_attribute_registry_bucket_name\": \"VolumetricEntityType\", \"private_attribute_entity_type_name\": \"GenericVolume\", \"private_attribute_id\": null, \"name\": \"fluid\", \"private_attribute_zone_boundary_names\": {\"items\": [\"fluid/Interface_solid\", \"fluid/centerbody\", \"fluid/farfield\"]}, \"private_attribute_full_name\": \"fluid\", \"axes\": null, \"axis\": null, \"center\": null}, {\"private_attribute_registry_bucket_name\": \"VolumetricEntityType\", \"private_attribute_entity_type_name\": \"GenericVolume\", \"private_attribute_id\": null, \"name\": \"solid\", \"private_attribute_zone_boundary_names\": {\"items\": [\"solid/Interface_fluid\", \"solid/adiabatic\"]}, \"private_attribute_full_name\": \"solid\", \"axes\": null, \"axis\": null, \"center\": null}], \"boundaries\": [{\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"fluid/farfield\", \"private_attribute_full_name\": \"fluid/farfield\", \"private_attribute_is_interface\": false, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"solid/Interface_fluid\", \"private_attribute_full_name\": \"solid/Interface_fluid\", \"private_attribute_is_interface\": true, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"fluid/centerbody\", \"private_attribute_full_name\": \"fluid/centerbody\", \"private_attribute_is_interface\": false, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"solid/adiabatic\", \"private_attribute_full_name\": \"solid/adiabatic\", \"private_attribute_is_interface\": false, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"fluid/Interface_solid\", \"private_attribute_full_name\": \"fluid/Interface_solid\", \"private_attribute_is_interface\": true, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}]}}}"
3+
"simulationJson": "{\"version\": \"25.2.1\", \"unit_system\": {\"name\": \"SI\"}, \"reference_geometry\": {\"moment_center\": {\"value\": [0.0, 0.0, 0.0], \"units\": \"m\"}, \"moment_length\": {\"value\": [1.0, 1.0, 1.0], \"units\": \"m\"}, \"area\": {\"value\": 1.0, \"units\": \"m**2\"}}, \"operating_condition\": {\"type_name\": \"AerospaceCondition\", \"private_attribute_constructor\": \"default\", \"private_attribute_input_cache\": {\"alpha\": {\"value\": 0.0, \"units\": \"degree\"}, \"beta\": {\"value\": 0.0, \"units\": \"degree\"}, \"thermal_state\": {\"type_name\": \"ThermalState\", \"private_attribute_constructor\": \"default\", \"private_attribute_input_cache\": {}, \"temperature\": {\"value\": 288.15, \"units\": \"K\"}, \"density\": {\"value\": 1.225, \"units\": \"kg/m**3\"}, \"material\": {\"type\": \"air\", \"name\": \"air\", \"dynamic_viscosity\": {\"reference_viscosity\": {\"value\": 1.716e-05, \"units\": \"Pa*s\"}, \"reference_temperature\": {\"value\": 273.15, \"units\": \"K\"}, \"effective_temperature\": {\"value\": 110.4, \"units\": \"K\"}}}}}, \"alpha\": {\"value\": 0.0, \"units\": \"degree\"}, \"beta\": {\"value\": 0.0, \"units\": \"degree\"}, \"thermal_state\": {\"type_name\": \"ThermalState\", \"private_attribute_constructor\": \"default\", \"private_attribute_input_cache\": {}, \"temperature\": {\"value\": 288.15, \"units\": \"K\"}, \"density\": {\"value\": 1.225, \"units\": \"kg/m**3\"}, \"material\": {\"type\": \"air\", \"name\": \"air\", \"dynamic_viscosity\": {\"reference_viscosity\": {\"value\": 1.716e-05, \"units\": \"Pa*s\"}, \"reference_temperature\": {\"value\": 273.15, \"units\": \"K\"}, \"effective_temperature\": {\"value\": 110.4, \"units\": \"K\"}}}}}, \"models\": [{\"type\": \"Wall\", \"entities\": {\"stored_entities\": []}, \"name\": \"Wall\", \"use_wall_function\": false, \"heat_spec\": {\"value\": {\"value\": 0.0, \"units\": \"W/m**2\"}, \"type_name\": \"HeatFlux\"}, \"roughness_height\": {\"value\": 0.0, \"units\": \"m\"}}, {\"type\": \"Freestream\", \"entities\": {\"stored_entities\": []}, \"name\": \"Freestream\"}, {\"material\": {\"type\": \"air\", \"name\": \"air\", \"dynamic_viscosity\": {\"reference_viscosity\": {\"value\": 1.716e-05, \"units\": \"Pa*s\"}, \"reference_temperature\": {\"value\": 273.15, \"units\": \"K\"}, \"effective_temperature\": {\"value\": 110.4, \"units\": \"K\"}}}, \"initial_condition\": {\"type_name\": \"NavierStokesInitialCondition\", \"rho\": \"rho\", \"u\": \"u\", \"v\": \"v\", \"w\": \"w\", \"p\": \"p\"}, \"type\": \"Fluid\", \"navier_stokes_solver\": {\"absolute_tolerance\": 1e-10, \"relative_tolerance\": 0.0, \"order_of_accuracy\": 2, \"equation_evaluation_frequency\": 1, \"linear_solver\": {\"max_iterations\": 30}, \"CFL_multiplier\": 1.0, \"kappa_MUSCL\": -1.0, \"numerical_dissipation_factor\": 1.0, \"limit_velocity\": false, \"limit_pressure_density\": false, \"type_name\": \"Compressible\", \"low_mach_preconditioner\": false, \"update_jacobian_frequency\": 4, \"max_force_jac_update_physical_steps\": 0}, \"turbulence_model_solver\": {\"absolute_tolerance\": 1e-08, \"relative_tolerance\": 0.0, \"order_of_accuracy\": 2, \"equation_evaluation_frequency\": 4, \"linear_solver\": {\"max_iterations\": 20}, \"CFL_multiplier\": 2.0, \"type_name\": \"SpalartAllmaras\", \"reconstruction_gradient_limiter\": 0.5, \"quadratic_constitutive_relation\": false, \"modeling_constants\": {\"type_name\": \"SpalartAllmarasConsts\", \"C_DES\": 0.72, \"C_d\": 8.0, \"C_cb1\": 0.1355, \"C_cb2\": 0.622, \"C_sigma\": 0.6666666666666666, \"C_v1\": 7.1, \"C_vonKarman\": 0.41, \"C_w2\": 0.3, \"C_t3\": 1.2, \"C_t4\": 0.5, \"C_min_rd\": 10.0}, \"update_jacobian_frequency\": 4, \"max_force_jac_update_physical_steps\": 0, \"rotation_correction\": false}, \"transition_model_solver\": {\"type_name\": \"None\"}}], \"time_stepping\": {\"type_name\": \"Steady\", \"max_steps\": 2000, \"CFL\": {\"type\": \"adaptive\", \"min\": 0.1, \"max\": 10000.0, \"max_relative_change\": 1.0, \"convergence_limiting_factor\": 0.25}}, \"user_defined_fields\": [], \"outputs\": [{\"frequency\": -1, \"frequency_offset\": 0, \"output_format\": \"paraview\", \"name\": \"Surface output\", \"entities\": {\"stored_entities\": [{\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"name\": \"*\", \"private_attribute_sub_components\": []}]}, \"write_single_file\": false, \"output_fields\": {\"items\": [\"Cp\", \"yPlus\", \"Cf\", \"CfVec\"]}, \"output_type\": \"SurfaceOutput\"}], \"private_attribute_asset_cache\": {\"project_length_unit\": {\"value\": 1.0, \"units\": \"m\"}, \"project_entity_info\": {\"type_name\": \"VolumeMeshEntityInfo\", \"zones\": [{\"private_attribute_registry_bucket_name\": \"VolumetricEntityType\", \"private_attribute_entity_type_name\": \"GenericVolume\", \"private_attribute_id\": \"fluid\", \"name\": \"fluid\", \"private_attribute_zone_boundary_names\": {\"items\": [\"fluid/Interface_solid\", \"fluid/centerbody\", \"fluid/farfield\"]}, \"private_attribute_full_name\": \"fluid\", \"axes\": null, \"axis\": null, \"center\": null}, {\"private_attribute_registry_bucket_name\": \"VolumetricEntityType\", \"private_attribute_entity_type_name\": \"GenericVolume\", \"private_attribute_id\": \"solid\", \"name\": \"solid\", \"private_attribute_zone_boundary_names\": {\"items\": [\"solid/Interface_fluid\", \"solid/adiabatic\"]}, \"private_attribute_full_name\": \"solid\", \"axes\": null, \"axis\": null, \"center\": null}], \"boundaries\": [{\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"fluid/farfield\", \"private_attribute_full_name\": \"fluid/farfield\", \"private_attribute_is_interface\": false, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"solid/Interface_fluid\", \"private_attribute_full_name\": \"solid/Interface_fluid\", \"private_attribute_is_interface\": true, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"fluid/centerbody\", \"private_attribute_full_name\": \"fluid/centerbody\", \"private_attribute_is_interface\": false, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"solid/adiabatic\", \"private_attribute_full_name\": \"solid/adiabatic\", \"private_attribute_is_interface\": false, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}, {\"private_attribute_registry_bucket_name\": \"SurfaceEntityType\", \"private_attribute_entity_type_name\": \"Surface\", \"private_attribute_id\": null, \"name\": \"fluid/Interface_solid\", \"private_attribute_full_name\": \"fluid/Interface_solid\", \"private_attribute_is_interface\": true, \"private_attribute_tag_key\": null, \"private_attribute_sub_components\": []}]}}}"
44
}
55
}

tests/simulation/test_project.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,39 @@ def test_root_asset_entity_change_reflection(mock_id, mock_response):
7979
== "red"
8080
)
8181

82+
# VolumeMesh: verify zone center/axis modifications flow through set_up_params_for_uploading
83+
# Simulate the customer scenario where self.volume is a DIFFERENT Python object than project._root_asset
84+
project_vm = fl.Project.from_cloud(project_id="prj-99cc6f96-15d3-4170-973c-a0cced6bf36b")
85+
separate_vm = VolumeMeshV2.from_cloud(id="vm-bff35714-41b1-4251-ac74-46a40b95a330")
86+
assert separate_vm is not project_vm._root_asset
87+
88+
zone = separate_vm["fluid"]
89+
zone.center = (1.2, 2.3, 3.4) * u.m
90+
zone.axis = (0, 1, 0)
91+
92+
with fl.SI_unit_system:
93+
vm_params = fl.SimulationParams(
94+
models=[
95+
fl.Rotation(
96+
name="testRotation",
97+
volumes=[zone],
98+
spec=fl.AngularVelocity(100 * fl.u.rpm),
99+
),
100+
],
101+
)
102+
vm_params = set_up_params_for_uploading(
103+
params=vm_params,
104+
root_asset=project_vm._root_asset,
105+
length_unit=project_vm.length_unit,
106+
use_beta_mesher=False,
107+
use_geometry_AI=False,
108+
)
109+
110+
entity_info = vm_params.private_attribute_asset_cache.project_entity_info
111+
fluid_zone = next(z for z in entity_info.zones if z.name == "fluid")
112+
assert all(fluid_zone.center == [1.2, 2.3, 3.4] * u.m)
113+
assert fluid_zone.axis == (0, 1, 0)
114+
82115

83116
def test_get_asset_with_id(mock_id, mock_response):
84117
project = fl.Project.from_cloud(project_id="prj-41d2333b-85fd-4bed-ae13-15dcb6da519e")

0 commit comments

Comments
 (0)