-
Notifications
You must be signed in to change notification settings - Fork 6
[FL-729] [FLPY-7] Dimensioned Output #1012
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
Open
benflexcompute
wants to merge
3
commits into
expressions
Choose a base branch
from
BenY/FL-729-DimensionedOutput
base: expressions
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,7 +80,18 @@ | |
translate_setting_and_apply_to_all_entities, | ||
update_dict_recursively, | ||
) | ||
from flow360.component.simulation.unit_system import LengthType | ||
from flow360.component.simulation.unit_system import ( | ||
CGS_unit_system, | ||
LengthType, | ||
SI_unit_system, | ||
UnitSystem, | ||
imperial_unit_system, | ||
unit_system_manager, | ||
) | ||
from flow360.component.simulation.user_code import ( | ||
ValueOrExpression, | ||
_solver_variable_values, | ||
) | ||
from flow360.component.simulation.utils import ( | ||
is_exact_instance, | ||
is_instance_of_type_in_union, | ||
|
@@ -218,6 +229,42 @@ def rotation_translator(model: Rotation): | |
return volume_zone | ||
|
||
|
||
def _expression_has_single_solver_variable(expression: ValueOrExpression): | ||
""" | ||
Check if the expression has a single solver variable. | ||
|
||
This is used to determine if the expression is meant to dump a single native variable with potential requested target unit system. Instead of being a UDF. | ||
""" | ||
return len(expression.solver_variable_names()) == 1 | ||
|
||
|
||
def _get_expression_udf_name(output_field: ValueOrExpression): | ||
if output_field.output_unit_system is None: | ||
output_field.output_unit_system = unit_system_manager.current.name | ||
|
||
# Note: 2 Possibilities: | ||
# 1. `ValueOrExpression` Single Solver Variable: Normal output with potential requested target unit system. | ||
# 2. `ValueOrExpression` with UserVariable: UDF written by user. | ||
if _expression_has_single_solver_variable(output_field): | ||
# 1. | ||
field_name = output_field.solver_variable_names()[0] | ||
else: | ||
# 2. | ||
raise NotImplementedError("UDF not supported yet") | ||
# TODO: Better heuristic for output field name? | ||
return field_name.partition("solution.")[2] + f"_{output_field.output_unit_system}" | ||
|
||
|
||
def _get_output_field_name(output_field: Union[ValueOrExpression, str]): | ||
"""Convert output field to string""" | ||
if isinstance(output_field, str): | ||
return output_field | ||
if isinstance(output_field, ValueOrExpression): | ||
return _get_expression_udf_name(output_field) | ||
|
||
raise NotImplementedError() | ||
|
||
|
||
def translate_output_fields( | ||
output_model: Union[ | ||
SurfaceOutput, | ||
|
@@ -232,11 +279,15 @@ def translate_output_fields( | |
], | ||
): | ||
"""Get output fields""" | ||
return {"outputFields": output_model.output_fields.items} | ||
output_fields = [] | ||
for output_field in output_model.output_fields.items: | ||
output_fields.append(_get_output_field_name(output_field)) | ||
|
||
return {"outputFields": output_fields} | ||
|
||
|
||
def surface_probe_setting_translation_func(entity: SurfaceProbeOutput): | ||
"""Translate non-entitties part of SurfaceProbeOutput""" | ||
"""Translate non-entities part of SurfaceProbeOutput""" | ||
dict_with_merged_output_fields = monitor_translator(entity) | ||
dict_with_merged_output_fields["surfacePatches"] = [ | ||
surface.full_name for surface in entity.target_surfaces.stored_entities | ||
|
@@ -350,11 +401,16 @@ def translate_volume_output( | |
is_average=volume_output_class is TimeAverageVolumeOutput, | ||
) | ||
# Get outputFields | ||
output_fields = [] | ||
|
||
for output_field in get_global_setting_from_first_instance( | ||
output_params, volume_output_class, "output_fields" | ||
).items: | ||
output_fields.append(_get_output_field_name(output_field)) | ||
|
||
volume_output.update( | ||
{ | ||
"outputFields": get_global_setting_from_first_instance( | ||
output_params, volume_output_class, "output_fields" | ||
).model_dump()["items"], | ||
"outputFields": output_fields, | ||
} | ||
) | ||
return volume_output | ||
|
@@ -505,6 +561,71 @@ def translate_acoustic_output(output_params: list): | |
return None | ||
|
||
|
||
def get_solver_variable_with_converted_unit( | ||
expression: ValueOrExpression, input_params: SimulationParams | ||
) -> ValueOrExpression: | ||
|
||
def _get_dimensionalization_factor( | ||
dimensionality, unit_system: UnitSystem, params: SimulationParams | ||
): | ||
""" | ||
Get the dimensionalization factor for each component. | ||
""" | ||
return params.convert_unit( | ||
unit_system[dimensionality], target_system="flow360_v2" | ||
).value.item() | ||
|
||
unit_system_name = ( | ||
expression.output_unit_system | ||
if expression.output_unit_system | ||
else unit_system_manager.current.name | ||
) | ||
|
||
solver_variable_name = expression.solver_variable_names()[0] | ||
dimensionality = None | ||
|
||
assert _expression_has_single_solver_variable(expression) | ||
|
||
# Step 0: Figure out the dimensionality of the solver variable. | ||
for variable_name, value in _solver_variable_values.items(): | ||
if variable_name == solver_variable_name: | ||
dimensionality = value.units.dimensions | ||
break | ||
|
||
if not dimensionality: | ||
raise Flow360TranslationError("Solver variable not found:", solver_variable_name) | ||
|
||
# Step 1: Get the target unit for the solver variable. | ||
unit_system = None | ||
|
||
if unit_system_name == "SI": | ||
unit_system = SI_unit_system | ||
elif unit_system_name == "Imperial": | ||
unit_system = imperial_unit_system | ||
elif unit_system_name == "CGS": | ||
unit_system = CGS_unit_system | ||
else: | ||
raise NotImplementedError() | ||
|
||
# Step 2: Get the dimensionalization factor for each component. | ||
conversion_constant = _get_dimensionalization_factor(dimensionality, unit_system, input_params) | ||
return expression / conversion_constant | ||
|
||
|
||
def assemble_udf_expression(expression: ValueOrExpression): | ||
solver_variable_name = expression.solver_variable_names()[0] | ||
for variable_name, value in _solver_variable_values.items(): | ||
if variable_name == solver_variable_name: | ||
n_dim = value.value.ndim | ||
break | ||
if n_dim == 0: | ||
return f"{_get_expression_udf_name(expression)} = {expression.to_solver_code()};" | ||
assembled_expression = "" | ||
for i_comp in range(len(value.value)): | ||
assembled_expression += f"{_get_expression_udf_name(expression)}[{i_comp}] = ({expression.to_solver_code()})[{i_comp}];" | ||
return assembled_expression | ||
|
||
|
||
def process_output_fields_for_udf(input_params: SimulationParams): | ||
""" | ||
Process all output fields from different output types and generate additional | ||
|
@@ -514,28 +635,39 @@ def process_output_fields_for_udf(input_params: SimulationParams): | |
input_params: SimulationParams object containing outputs configuration | ||
|
||
Returns: | ||
tuple: (all_field_names, generated_udfs) where: | ||
- all_field_names is a set of all output field names | ||
tuple: (all_fields, generated_udfs) where: | ||
- all_fields is a set of all output field names | ||
- generated_udfs is a list of UserDefinedField objects for dimensioned fields | ||
""" | ||
|
||
# Collect all output field names from all output types | ||
all_field_names = set() | ||
all_fields = set() | ||
|
||
if input_params.outputs: | ||
for output in input_params.outputs: | ||
if hasattr(output, "output_fields") and output.output_fields: | ||
all_field_names.update(output.output_fields.items) | ||
all_fields.update(output.output_fields.items) | ||
|
||
if isinstance(input_params.operating_condition, LiquidOperatingCondition): | ||
all_field_names.add("velocity_magnitude") | ||
all_fields.add("velocity_magnitude") | ||
|
||
# Generate UDFs for dimensioned fields | ||
generated_udfs = [] | ||
for field_name in all_field_names: | ||
udf_expression = generate_predefined_udf(field_name, input_params) | ||
if udf_expression: | ||
generated_udfs.append(UserDefinedField(name=field_name, expression=udf_expression)) | ||
for field in all_fields: | ||
if isinstance(field, str): | ||
udf_expression = generate_predefined_udf(field, input_params) | ||
if udf_expression: | ||
generated_udfs.append(UserDefinedField(name=field, expression=udf_expression)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
|
||
elif isinstance(field, ValueOrExpression): | ||
converted_expression = get_solver_variable_with_converted_unit(field, input_params) | ||
assembled_string = assemble_udf_expression(converted_expression) | ||
generated_udfs.append( | ||
UserDefinedField( | ||
name=_get_expression_udf_name(field), | ||
expression=assembled_string, | ||
) | ||
) | ||
|
||
return generated_udfs | ||
|
||
|
@@ -1286,13 +1418,14 @@ def get_solver_json( | |
) | ||
|
||
##:: Step 4: Get outputs (has to be run after the boundaries are translated) | ||
|
||
translated = translate_output(input_params, translated) | ||
with input_params.unit_system: | ||
translated = translate_output(input_params, translated) | ||
|
||
##:: Step 5: Get user defined fields and auto-generated fields for dimensioned output | ||
translated["userDefinedFields"] = [] | ||
# Add auto-generated UDFs for dimensioned fields | ||
generated_udfs = process_output_fields_for_udf(input_params) | ||
with input_params.unit_system: | ||
generated_udfs = process_output_fields_for_udf(input_params) | ||
|
||
# Add user-specified UDFs and auto-generated UDFs for dimensioned fields | ||
for udf in [*input_params.user_defined_fields, *generated_udfs]: | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since ValueOrExpression is meant to be used as a generic type (e.g.
ValueOrExpression[LengthType.Vector]
etc..) it might be better to just useExpression
here (I think its functionally identical?)... As I understand pure values here (like4 * u.m / u.s
) make no sense here, so the field type might be slightly misleading.