Skip to content

UserVariable as Token and value from context #1161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 17, 2025
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
4 changes: 4 additions & 0 deletions flow360/component/project_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from flow360.component.simulation.primitives import Box, Cylinder, GhostSurface
from flow360.component.simulation.simulation_params import SimulationParams
from flow360.component.simulation.unit_system import LengthType
from flow360.component.simulation.user_code.core.types import save_user_variables
from flow360.component.simulation.utils import model_attribute_unlock
from flow360.component.simulation.web.asset_base import AssetBase
from flow360.component.utils import parse_datetime
Expand Down Expand Up @@ -281,6 +282,9 @@ def set_up_params_for_uploading(

params = _set_up_default_reference_geometry(params, length_unit)

# Convert all reference of UserVariables to VariableToken
params = save_user_variables(params)

return params


Expand Down
21 changes: 18 additions & 3 deletions flow360/component/simulation/blueprint/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,17 @@ def get_data_model(self, name: str) -> Optional[pd.BaseModel]:
return self._data_models[name]

def set_alias(self, name, alias) -> None:
"""Set alias used for code generation."""
"""
Set alias used for code generation.
This is meant for non-user variables.
"""
self._aliases[name] = alias

def get_alias(self, name) -> Optional[str]:
"""Get alias used for code generation."""
"""
Get alias used for code generation.
This is meant for non-user variables.
"""
return self._aliases.get(name)

def set(self, name: str, value: Any, data_model: pd.BaseModel = None) -> None:
Expand All @@ -94,7 +100,7 @@ def set(self, name: str, value: Any, data_model: pd.BaseModel = None) -> None:
Args:
name (str): The variable name to set.
value (Any): The value to assign.
data_model (BaseModel, optional): The type of the associate with this entry
data_model (BaseModel, optional): The type of the associate with this entry (for non-user variables)
"""
self._values[name] = value

Expand Down Expand Up @@ -137,3 +143,12 @@ def copy(self) -> "EvaluationContext":
of the current variable values.
"""
return EvaluationContext(self._resolver, dict(self._values))

@property
def user_variable_names(self):
"""Get the set of user variables in the context."""
return {name for name in self._values.keys() if "." not in name}

def clear(self):
"""Clear user variables from the context."""
self._values = {name: value for name, value in self._values.items() if "." in name}
14 changes: 11 additions & 3 deletions flow360/component/simulation/framework/param_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""pre processing and post processing utilities for simulation parameters."""

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

import pydantic as pd

Expand All @@ -18,9 +18,17 @@
_VolumeEntityBase,
)
from flow360.component.simulation.unit_system import LengthType
from flow360.component.simulation.user_code.core.types import UserVariable
from flow360.component.simulation.user_code.core.types import (
VariableContextInfo,
update_global_context,
)
from flow360.component.simulation.utils import model_attribute_unlock

VariableContextList = Annotated[
List[VariableContextInfo],
pd.AfterValidator(update_global_context),
]


class AssetCache(Flow360BaseModel):
"""
Expand All @@ -39,7 +47,7 @@ class AssetCache(Flow360BaseModel):
use_geometry_AI: bool = pd.Field(
False, description="Flag whether user requested the use of GAI."
)
project_variables: Optional[List[UserVariable]] = pd.Field(
project_variables: Optional[VariableContextList] = pd.Field(
None, description="List of user variables that are used in all the `Expression` instances."
)

Expand Down
90 changes: 43 additions & 47 deletions flow360/component/simulation/outputs/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,26 @@ def _validate_non_liquid_output_fields(cls, value: UniqueItemList):
)
return value

@pd.field_validator("output_fields", mode="after")
@pd.field_validator("output_fields", mode="before")
@classmethod
def _convert_solver_variables_as_user_variables(cls, value: UniqueItemList):
for i, output_item in enumerate(value.items):
if isinstance(output_item, SolverVariable):
# Strip out any prefix before a dot in the name
name = (
output_item.name.split(".")[-1] if "." in output_item.name else output_item.name
)
value.items[i] = UserVariable(name=name, value=output_item)
def _convert_solver_variables_as_user_variables(cls, value):
# Handle both dict/list (deserialization) and UniqueItemList (python object)
def solver_variable_to_user_variable(item):
if isinstance(item, SolverVariable):
name = item.name.split(".")[-1] if "." in item.name else item.name
return UserVariable(name=name, value=item)
return item

# If input is a dict (from deserialization so no SolverVariable expected)
if isinstance(value, dict):
return value
# If input is a list (from Python mode)
if isinstance(value, list):
return [solver_variable_to_user_variable(item) for item in value]
# If input is a UniqueItemList (python object)
if hasattr(value, "items") and isinstance(value.items, list):
value.items = [solver_variable_to_user_variable(item) for item in value.items]
return value
return value


Expand Down Expand Up @@ -209,11 +219,9 @@ class SurfaceOutput(_AnimationAndFileFormatSettings):
+ "Will choose the value of the last instance of this option of the same output type "
+ "(:class:`SurfaceOutput` or :class:`TimeAverageSurfaceOutput`) in the output list.",
)
output_fields: UniqueItemList[Union[SurfaceFieldNames, str, UserVariable, SolverVariable]] = (
pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
+ " :ref:`variables specific to SurfaceOutput<SurfaceSpecificVariablesV2>` and :class:`UserDefinedField`."
)
output_fields: UniqueItemList[Union[SurfaceFieldNames, str, UserVariable]] = pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
+ " :ref:`variables specific to SurfaceOutput<SurfaceSpecificVariablesV2>` and :class:`UserDefinedField`."
)
output_type: Literal["SurfaceOutput"] = pd.Field("SurfaceOutput", frozen=True)

Expand Down Expand Up @@ -278,12 +286,10 @@ class VolumeOutput(_AnimationAndFileFormatSettings):
"""

name: Optional[str] = pd.Field("Volume output", description="Name of the `VolumeOutput`.")
output_fields: UniqueItemList[Union[VolumeFieldNames, str, UserVariable, SolverVariable]] = (
pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to VolumeOutput<VolumeAndSliceSpecificVariablesV2>`"
" and :class:`UserDefinedField`."
)
output_fields: UniqueItemList[Union[VolumeFieldNames, str, UserVariable]] = pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to VolumeOutput<VolumeAndSliceSpecificVariablesV2>`"
" and :class:`UserDefinedField`."
)
output_type: Literal["VolumeOutput"] = pd.Field("VolumeOutput", frozen=True)

Expand Down Expand Up @@ -348,12 +354,10 @@ class SliceOutput(_AnimationAndFileFormatSettings):
alias="slices",
description="List of output :class:`~flow360.Slice` entities.",
)
output_fields: UniqueItemList[Union[SliceFieldNames, str, UserVariable, SolverVariable]] = (
pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to SliceOutput<VolumeAndSliceSpecificVariablesV2>`"
" and :class:`UserDefinedField`."
)
output_fields: UniqueItemList[Union[SliceFieldNames, str, UserVariable]] = pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to SliceOutput<VolumeAndSliceSpecificVariablesV2>`"
" and :class:`UserDefinedField`."
)
output_type: Literal["SliceOutput"] = pd.Field("SliceOutput", frozen=True)

Expand Down Expand Up @@ -436,11 +440,9 @@ class IsosurfaceOutput(_AnimationAndFileFormatSettings):
alias="isosurfaces",
description="List of :class:`~flow360.Isosurface` entities.",
)
output_fields: UniqueItemList[Union[CommonFieldNames, str, UserVariable, SolverVariable]] = (
pd.Field(
description="List of output variables. Including "
":ref:`universal output variables<UniversalVariablesV2>` and :class:`UserDefinedField`."
)
output_fields: UniqueItemList[Union[CommonFieldNames, str, UserVariable]] = pd.Field(
description="List of output variables. Including "
":ref:`universal output variables<UniversalVariablesV2>` and :class:`UserDefinedField`."
)
output_type: Literal["IsosurfaceOutput"] = pd.Field("IsosurfaceOutput", frozen=True)

Expand Down Expand Up @@ -475,7 +477,7 @@ class SurfaceIntegralOutput(_OutputBase):
alias="surfaces",
description="List of boundaries where the surface integral will be calculated.",
)
output_fields: UniqueItemList[Union[str, UserVariable, SolverVariable]] = pd.Field(
output_fields: UniqueItemList[Union[str, UserVariable]] = pd.Field(
description="List of output variables, only the :class:`UserDefinedField` is allowed."
)
output_type: Literal["SurfaceIntegralOutput"] = pd.Field("SurfaceIntegralOutput", frozen=True)
Expand Down Expand Up @@ -539,11 +541,9 @@ class ProbeOutput(_OutputBase):
+ "monitor group. :class:`~flow360.PointArray` is used to "
+ "define monitored points along a line.",
)
output_fields: UniqueItemList[Union[CommonFieldNames, str, UserVariable, SolverVariable]] = (
pd.Field(
description="List of output fields. Including :ref:`universal output variables<UniversalVariablesV2>`"
" and :class:`UserDefinedField`."
)
output_fields: UniqueItemList[Union[CommonFieldNames, str, UserVariable]] = pd.Field(
description="List of output fields. Including :ref:`universal output variables<UniversalVariablesV2>`"
" and :class:`UserDefinedField`."
)
output_type: Literal["ProbeOutput"] = pd.Field("ProbeOutput", frozen=True)

Expand Down Expand Up @@ -605,11 +605,9 @@ class SurfaceProbeOutput(Flow360BaseModel):
+ "entities belonging to this monitor group."
)

output_fields: UniqueItemList[Union[SurfaceFieldNames, str, UserVariable, SolverVariable]] = (
pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to SurfaceOutput<SurfaceSpecificVariablesV2>` and :class:`UserDefinedField`."
)
output_fields: UniqueItemList[Union[SurfaceFieldNames, str, UserVariable]] = pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to SurfaceOutput<SurfaceSpecificVariablesV2>` and :class:`UserDefinedField`."
)
output_type: Literal["SurfaceProbeOutput"] = pd.Field("SurfaceProbeOutput", frozen=True)

Expand All @@ -636,11 +634,9 @@ class SurfaceSliceOutput(_AnimationAndFileFormatSettings):

output_format: Literal["paraview"] = pd.Field(default="paraview")

output_fields: UniqueItemList[Union[SurfaceFieldNames, str, UserVariable, SolverVariable]] = (
pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to SurfaceOutput<SurfaceSpecificVariablesV2>` and :class:`UserDefinedField`."
)
output_fields: UniqueItemList[Union[SurfaceFieldNames, str, UserVariable]] = pd.Field(
description="List of output variables. Including :ref:`universal output variables<UniversalVariablesV2>`,"
" :ref:`variables specific to SurfaceOutput<SurfaceSpecificVariablesV2>` and :class:`UserDefinedField`."
)
output_type: Literal["SurfaceSliceOutput"] = pd.Field("SurfaceSliceOutput", frozen=True)

Expand Down
6 changes: 0 additions & 6 deletions flow360/component/simulation/simulation_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@
_check_time_average_output,
_check_unsteadiness_to_use_hybrid_model,
_check_valid_models_for_liquid,
_save_project_variables,
)
from flow360.component.utils import remove_properties_by_name
from flow360.error_messages import (
Expand Down Expand Up @@ -451,11 +450,6 @@ def check_duplicate_user_defined_fields(cls, v):

return v

@pd.model_validator(mode="after")
def save_project_variables(self):
"""Populate project variables private attribute used in the simulation params"""
return _save_project_variables(self)

@pd.model_validator(mode="after")
def check_cht_solver_settings(self):
"""Check the Conjugate Heat Transfer settings, transferred from checkCHTSolverSettings"""
Expand Down
Loading