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
63 changes: 63 additions & 0 deletions flow360/component/simulation/framework/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,68 @@ def _to_25_7_6(params_as_dict):
return remove_entity_bucket_field(params_as_dict=params_as_dict)


def _to_25_7_7(params_as_dict):
"""
1. Reset frequency and frequency_offset to defaults for steady simulations
2. Remove invalid output fields based on transition model
"""

# 1. Handle frequency settings in steady simulations
if params_as_dict.get("time_stepping", {}).get("type_name") == "Steady":
outputs = params_as_dict.get("outputs") or []
for output in outputs:
# Output types that have frequency/frequency_offset settings
if output.get("output_type") in [
"VolumeOutput",
"TimeAverageVolumeOutput",
"SurfaceOutput",
"TimeAverageSurfaceOutput",
"SliceOutput",
"TimeAverageSliceOutput",
"IsosurfaceOutput",
"TimeAverageIsosurfaceOutput",
"SurfaceSliceOutput",
]:
# Reset to defaults: frequency=-1, frequency_offset=0
if "frequency" in output:
output["frequency"] = -1
if "frequency_offset" in output:
output["frequency_offset"] = 0

# 2. Remove invalid output fields based on transition model
# Get transition model type from models
transition_model_type = "None"
models = params_as_dict.get("models") or []
for model in models:
if model.get("type") == "Fluid":
transition_solver = model.get("transition_model_solver") or {}
transition_model_type = transition_solver.get("type_name")
break

# If transition model is None or not found, remove transition-specific fields
if transition_model_type == "None":
transition_output_fields = [
"residualTransition",
"solutionTransition",
"linearResidualTransition",
]

outputs = params_as_dict.get("outputs") or []
for output in outputs:
if output.get("output_type") in ["AeroAcousticOutput", "StreamlineOutput"]:
continue
if "output_fields" in output:
output_fields = output["output_fields"]
if isinstance(output_fields, dict) and "items" in output_fields:
items = output_fields["items"]
# Remove invalid fields
output_fields["items"] = [
field for field in items if field not in transition_output_fields
]

return params_as_dict


VERSION_MILESTONES = [
(Flow360Version("24.11.1"), _to_24_11_1),
(Flow360Version("24.11.7"), _to_24_11_7),
Expand All @@ -400,6 +462,7 @@ def _to_25_7_6(params_as_dict):
(Flow360Version("25.6.6"), _to_25_6_6),
(Flow360Version("25.7.2"), _to_25_7_2),
(Flow360Version("25.7.6"), _to_25_7_6),
(Flow360Version("25.7.7"), _to_25_7_7),
] # A list of the Python API version tuple with there corresponding updaters.


Expand Down
14 changes: 14 additions & 0 deletions flow360/component/simulation/outputs/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,20 @@ class _AnimationSettings(_OutputBase):
+ " 0 is at beginning of simulation.",
)

@pd.field_validator("frequency", "frequency_offset", mode="after")
@classmethod
def disable_frequency_settings_in_steady_simulation(cls, value, info: pd.ValidationInfo):
"""Disable frequency settings in a steady simulation"""
validation_info = get_validation_info()
if validation_info is None or validation_info.time_stepping != TimeSteppingType.STEADY:
return value
# pylint: disable=unsubscriptable-object
if value != cls.model_fields[info.field_name].default:
raise ValueError(
f"Output {info.field_name} cannot be specified in a steady simulation."
)
return value


class _AnimationAndFileFormatSettings(_AnimationSettings):
"""
Expand Down
6 changes: 6 additions & 0 deletions flow360/component/simulation/simulation_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
from flow360.component.simulation.utils import model_attribute_unlock
from flow360.component.simulation.validation.validation_output import (
_check_output_fields,
_check_output_fields_valid_given_transition_model,
_check_output_fields_valid_given_turbulence_model,
_check_unique_surface_volume_probe_entity_names,
_check_unique_surface_volume_probe_names,
Expand Down Expand Up @@ -546,6 +547,11 @@ def check_output_fields_valid_given_turbulence_model(params):
"""Check output fields are valid given the turbulence model"""
return _check_output_fields_valid_given_turbulence_model(params)

@pd.model_validator(mode="after")
def check_output_fields_valid_given_transition_model(params):
"""Check output fields are valid given the transition model"""
return _check_output_fields_valid_given_transition_model(params)

@pd.model_validator(mode="after")
def check_and_add_rotating_reference_frame_model_flag_in_volumezones(params):
"""Ensure that all volume zones have the rotating_reference_frame_model flag with correct values"""
Expand Down
33 changes: 33 additions & 0 deletions flow360/component/simulation/validation/validation_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,39 @@ def _check_output_fields_valid_given_turbulence_model(params):
return params


def _check_output_fields_valid_given_transition_model(params):
"""Ensure that the output fields are consistent with the transition model used."""

if not params.models or not params.outputs:
return params

transition_model = "None"
for model in params.models:
if isinstance(model, Fluid):
transition_model = model.transition_model_solver.type_name
break

if transition_model != "None":
return params

transition_output_fields = [
"residualTransition",
"solutionTransition",
"linearResidualTransition",
]

for output_index, output in enumerate(params.outputs):
if output.output_type in ("AeroAcousticOutput", "StreamlineOutput"):
continue
for item in output.output_fields.items:
if isinstance(item, str) and item in transition_output_fields:
raise ValueError(
f"In `outputs`[{output_index}] {output.output_type}:, {item} is not a valid"
f" output field when transition model is not used."
)
return params


def _check_unsteadiness_to_use_aero_acoustics(params):

if not params.outputs:
Expand Down
2 changes: 1 addition & 1 deletion flow360/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
version
"""

__version__ = "25.7.6"
__version__ = "25.7.7"
__solver_version__ = "release-25.7"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "flow360"
version = "v25.7.6"
version = "v25.7.7"
description = "Flow360 Python Client"
authors = ["Flexcompute <support@flexcompute.com>"]

Expand Down
2 changes: 1 addition & 1 deletion tests/ref/simulation/service_init_geometry.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/ref/simulation/service_init_surface_mesh.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/ref/simulation/service_init_volume_mesh.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/simulation/converter/ref/ref_monitor.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/simulation/data/simulation_by_face_id.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/simulation/params/data/geometry/simulation.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down
117 changes: 117 additions & 0 deletions tests/simulation/params/test_validators_output.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json
import os
import re

import pytest
Expand Down Expand Up @@ -41,6 +43,7 @@
from flow360.component.simulation.user_code.core.types import UserVariable
from flow360.component.simulation.user_code.functions import math
from flow360.component.simulation.user_code.variables import solution
from flow360.component.volume_mesh import VolumeMeshV2


@pytest.fixture()
Expand Down Expand Up @@ -142,6 +145,57 @@ def test_turbulence_enabled_output_fields():
)


def test_transition_model_enabled_output_fields():
with pytest.raises(
ValueError,
match=re.escape(
"In `outputs`[0] IsosurfaceOutput:, solutionTransition is not a valid output field when transition model is not used."
),
):
with imperial_unit_system:
SimulationParams(
models=[Fluid(transition_model_solver=NoneSolver())],
outputs=[
IsosurfaceOutput(
name="iso",
entities=[Isosurface(name="tmp", field="mut", iso_value=1)],
output_fields=["solutionTransition"],
)
],
)

with pytest.raises(
ValueError,
match=re.escape(
"In `outputs`[0] SurfaceProbeOutput:, residualTransition is not a valid output field when transition model is not used."
),
):
with imperial_unit_system:
SimulationParams(
models=[Fluid(transition_model_solver=NoneSolver())],
outputs=[
SurfaceProbeOutput(
name="probe_output",
probe_points=[Point(name="point_1", location=[1, 2, 3] * u.m)],
output_fields=["residualTransition"],
target_surfaces=[Surface(name="fluid/body")],
)
],
)

with pytest.raises(
ValueError,
match=re.escape(
"In `outputs`[0] VolumeOutput:, linearResidualTransition is not a valid output field when transition model is not used."
),
):
with imperial_unit_system:
SimulationParams(
models=[Fluid(transition_model_solver=NoneSolver())],
outputs=[VolumeOutput(output_fields=["linearResidualTransition"])],
)


def test_surface_user_variables_in_output_fields():
uv_surface1 = UserVariable(
name="uv_surface1", value=math.dot(solution.velocity, solution.CfVec)
Expand Down Expand Up @@ -484,3 +538,66 @@ def test_surface_integral_entity_types():
),
],
)


def test_output_frequency_settings_in_steady_simulation():
volume_mesh = VolumeMeshV2.from_local_storage(
mesh_id=None,
local_storage_path=os.path.join(
os.path.dirname(__file__), "..", "data", "vm_entity_provider"
),
)
with open(
os.path.join(
os.path.dirname(__file__), "..", "data", "vm_entity_provider", "simulation.json"
),
"r",
) as fh:
asset_cache_data = json.load(fh).pop("private_attribute_asset_cache")
asset_cache = AssetCache.model_validate(asset_cache_data)
with imperial_unit_system:
params = SimulationParams(
models=[Wall(name="wall", entities=volume_mesh["*"])],
time_stepping=Steady(),
outputs=[
VolumeOutput(
output_fields=["Mach", "Cp"],
frequency=2,
),
SurfaceOutput(
output_fields=["Cp"],
entities=volume_mesh["*"],
frequency_offset=10,
),
],
private_attribute_asset_cache=asset_cache,
)

params_as_dict = params.model_dump(exclude_none=True, mode="json")
params, errors, _ = validate_model(
params_as_dict=params_as_dict,
validated_by=ValidationCalledBy.LOCAL,
root_item_type="VolumeMesh",
validation_level="All",
)

expected_errors = [
{
"loc": ("outputs", 0, "frequency"),
"type": "value_error",
"msg": "Value error, Output frequency cannot be specified in a steady simulation.",
"ctx": {"relevant_for": ["Case"]},
},
{
"loc": ("outputs", 1, "frequency_offset"),
"type": "value_error",
"msg": "Value error, Output frequency_offset cannot be specified in a steady simulation.",
"ctx": {"relevant_for": ["Case"]},
},
]
assert len(errors) == len(expected_errors)
for err, exp_err in zip(errors, expected_errors):
assert err["loc"] == exp_err["loc"]
assert err["type"] == exp_err["type"]
assert err["ctx"]["relevant_for"] == exp_err["ctx"]["relevant_for"]
assert err["msg"] == exp_err["msg"]
2 changes: 0 additions & 2 deletions tests/simulation/params/test_validators_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -1697,7 +1697,6 @@ def test_validate_liquid_operating_condition():
],
outputs=[
VolumeOutput(
frequency=1,
output_format="both",
output_fields=["four"],
),
Expand Down Expand Up @@ -2144,7 +2143,6 @@ def test_redefined_user_defined_fields():
),
outputs=[
VolumeOutput(
frequency=1,
output_format="both",
output_fields=["pressure"],
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/simulation/service/data/simulation.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6b0",
"version": "25.7.7b0",
"unit_system": {
"name": "CGS"
},
Expand Down
2 changes: 1 addition & 1 deletion tests/simulation/service/params.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "25.7.6",
"version": "25.7.7",
"unit_system": {
"name": "SI"
},
Expand Down
Loading