Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions flow360/component/simulation/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os
from enum import Enum
from numbers import Number
from typing import Any, Collection, Dict, Literal, Optional, Tuple, Union
from typing import Any, Collection, Dict, Iterable, Literal, Optional, Tuple, Union

import numpy as np
import pydantic as pd
Expand Down Expand Up @@ -323,6 +323,49 @@ def _insert_forward_compatibility_notice(
return validation_errors


def initialize_variable_space(value: dict):
"""Load all user variables from private attributes when a simulation params object is initialized"""
if "private_attribute_asset_cache" not in value.keys():
return value
asset_cache: dict = value["private_attribute_asset_cache"]
if "project_variables" not in asset_cache.keys():
return value
if not isinstance(asset_cache["project_variables"], Iterable):
return value

clear_context()

# ==== Build dependency graph and sort variables ====
dependency_graph = DependencyGraph()
# Pad the project variables into proper schema
variable_list = []
for var in asset_cache["project_variables"]:
if "expression" in var["value"]:
# Expression type
variable_list.append({"name": var["name"], "value": var["value"]["expression"]})
else:
# Number type (#units ignored since it does not affect the dependency graph)
variable_list.append({"name": var["name"], "value": str(var["value"]["value"])})
dependency_graph.load_from_list(variable_list)
sorted_variables = dependency_graph.topology_sort()

for variable_name in sorted_variables:
variable_dict = next(
(var for var in asset_cache["project_variables"] if var["name"] == variable_name),
None,
)
if variable_dict is None:
continue
value_or_expression = {
key: value for key, value in variable_dict["value"].items() if key != "postProcessing"
}
UserVariable(
name=variable_dict["name"],
value=value_or_expression,
)
return value


def validate_model(
*,
params_as_dict,
Expand Down Expand Up @@ -376,7 +419,7 @@ def validate_model(

updated_param_as_dict = parse_model_dict(updated_param_as_dict, globals())

SimulationParams.initialize_variable_space(updated_param_as_dict)
initialize_variable_space(updated_param_as_dict)

additional_info = ParamsValidationInfo(param_as_dict=updated_param_as_dict)
with ValidationContext(levels=validation_levels_to_use, info=additional_info):
Expand Down
50 changes: 2 additions & 48 deletions flow360/component/simulation/simulation_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

from __future__ import annotations

from typing import Annotated, Iterable, List, Optional, Union
from typing import Annotated, List, Optional, Union

import pydantic as pd
import unyt as u

from flow360.component.simulation.blueprint.core.dependency_graph import DependencyGraph
from flow360.component.simulation.conversion import (
LIQUID_IMAGINARY_FREESTREAM_MACH,
unit_converter,
Expand Down Expand Up @@ -71,7 +70,6 @@
unit_system_manager,
unyt_quantity,
)
from flow360.component.simulation.user_code.core.types import UserVariable
from flow360.component.simulation.user_defined_dynamics.user_defined_dynamics import (
UserDefinedDynamic,
)
Expand Down Expand Up @@ -380,55 +378,11 @@ def convert_unit(
converted = value.in_base(unit_system=target_system)
return converted

# We have no way forcing validator call order so this is a workaround
@classmethod
def initialize_variable_space(cls, value: dict):
"""Load all user variables from private attributes when a simulation params object is initialized"""
if "private_attribute_asset_cache" not in value.keys():
return value
asset_cache: dict = value["private_attribute_asset_cache"]
if "project_variables" not in asset_cache.keys():
return value
if not isinstance(asset_cache["project_variables"], Iterable):
return value

# ==== Build dependency graph and sort variables ====
dependency_graph = DependencyGraph()
# Pad the project variables into proper schema
variable_list = []
for var in asset_cache["project_variables"]:
if "expression" in var["value"]:
# Expression type
variable_list.append({"name": var["name"], "value": var["value"]["expression"]})
else:
# Number type (#units ignored since it does not affect the dependency graph)
variable_list.append({"name": var["name"], "value": str(var["value"]["value"])})
dependency_graph.load_from_list(variable_list)
sorted_variables = dependency_graph.topology_sort()

for variable_name in sorted_variables:
variable_dict = next(
(var for var in asset_cache["project_variables"] if var["name"] == variable_name),
None,
)
if variable_dict is None:
continue
value_or_expression = {
key: value
for key, value in variable_dict["value"].items()
if key != "postProcessing"
}
UserVariable(
name=variable_dict["name"],
value=value_or_expression,
)
return value

# pylint: disable=no-self-argument
@pd.field_validator("models", mode="after")
@classmethod
def apply_default_fluid_settings(cls, v):
"""Apply default Fluid() settings if not found in models"""
"""Apply default Fluid() settings if not found in mode`ls"""
if v is None:
v = []
assert isinstance(v, list)
Expand Down
2 changes: 1 addition & 1 deletion flow360/component/simulation/user_code/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def set_value(cls, values):

if diff:
raise ValueError(
f"Redeclaring user variable {values['name']} with new value: {new_value}. "
f"Redeclaring user variable '{values['name']}' with new value: {new_value}. "
f"Previous value: {default_context.get(values['name'])}"
)
# Call the setter
Expand Down
2 changes: 1 addition & 1 deletion tests/simulation/test_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,7 @@ def test_overwriting_project_variables():

with pytest.raises(
ValueError,
match="Redeclaring user variable a with new value: 2.0. Previous value: 1.0",
match="Redeclaring user variable 'a' with new value: 2.0. Previous value: 1.0",
):
UserVariable(name="a", value=2)

Expand Down