Skip to content

Commit

Permalink
topology based coupling scheme (use type from topology) (#84)
Browse files Browse the repository at this point in the history
* Use type to determine implicit/explicit coupling else fallback to old code

* First try to create a test for type checking

* Working topology variation rest does not work yet

* make test work

* Also read in type from exchanges

* Use the type now to create config coupling type

* Revert renaming and use right error thrower

* If no valid type input throw error and return none

* Default back to old logic if no type was specified

* Only Process couplings only if no explicit coupling type is provided. !! I trust the comment # the couplings are added to the participants already here !!!

* Revert "Only Process couplings only if no explicit coupling type is provided. !! I trust the comment # the couplings are added to the participants already here !!!"

This reverts commit e47a51c.
Reverted because of error:
2025-02-02 21:53:51 ⚠️ [WARNING] Participant 'Solid' is missing a 'read-data' element.
2025-02-02 21:53:51 ⚠️ [WARNING] Participant 'Solid' is missing a 'write-data' element.
That means the comment that we already added couplings was wrong ?

* expand the test cases in test_ps_precice_config.py to cover more scenarios

* Fixe mixed outputing type none

* add the test case where there are no types in the topology

* Rename file and class name to actually tell what they are doing

* Fix test named .py.py
  • Loading branch information
Toddelismyname authored Feb 3, 2025
1 parent 35c3e89 commit 467f2b1
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 8 deletions.
25 changes: 17 additions & 8 deletions controller_utils/precice_struct/PS_PreCICEConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ def create_config(self, user_input: UI_UserInput):

# participants
for participant_name in user_input.participants:
# print("particip:", participant_name)
participant_obj = user_input.participants[participant_name]
list = participant_obj.list_of_couplings
self.solvers[participant_name] = PS_ParticipantSolver(participant_obj, list[0], self)
Expand All @@ -114,14 +113,14 @@ def create_config(self, user_input: UI_UserInput):
participant1_solver = self.solvers[participant1_name]
participant2_solver = self.solvers[participant2_name]
max_coupling_value = min(max_coupling_value, coupling.coupling_type.value)

# ========== FSI =========
if coupling.coupling_type == UI_CouplingType.fsi:
# VERY IMPORTANT: we rely here on the fact that the participants are sorted alphabetically
participant1_solver.make_participant_fsi_fluid(
self, coupling.boundaryC1, coupling.boundaryC2, participant2_solver.name )
participant2_solver.make_participant_fsi_structure(
self, coupling.boundaryC1, coupling.boundaryC2, participant1_solver.name)
# print(" FSI s1:", participant1_name, " s2:", participant2_name)
pass
# ========== F2S =========
if coupling.coupling_type == UI_CouplingType.f2s:
Expand All @@ -141,13 +140,23 @@ def create_config(self, user_input: UI_UserInput):
pass
pass

# if we have one conjugate heat or FSI then we must use implicit coupling
# print(" COUPLING VALUE = ", max_coupling_value)
if max_coupling_value < 2:
self.couplingScheme = PS_ImplicitCoupling()
# Determine coupling scheme based on new coupling type logic or existing max_coupling_value
if hasattr(user_input, 'coupling_type') and user_input.coupling_type is not None:
if user_input.coupling_type == 'strong':
self.couplingScheme = PS_ImplicitCoupling()
elif user_input.coupling_type == 'weak':
self.couplingScheme = PS_ExplicitCoupling()
else:
# Fallback to existing logic if invalid type
self.couplingScheme = PS_ImplicitCoupling() if max_coupling_value < 2 else PS_ExplicitCoupling()
else:
self.couplingScheme = PS_ExplicitCoupling()
self.couplingScheme.initFromUI( user_input, self )
# Use existing logic if no coupling type specified
self.couplingScheme = PS_ImplicitCoupling() if max_coupling_value < 2 else PS_ExplicitCoupling()
#throw an error if no coupling type is specified and the coupling scheme is not compatible with the coupling type
#raise ValueError("No coupling type specified and coupling scheme is not compatible with the coupling type " + ("explicit" if self.couplingScheme is PS_ExplicitCoupling() else "implicit"))

# Initialize coupling scheme with user input
self.couplingScheme.initFromUI(user_input, self)

pass

Expand Down
22 changes: 22 additions & 0 deletions controller_utils/ui_struct/UI_UserInput.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ def init_from_yaml(self, etree, mylog: UT_PCErrorLogging):
self.sim_info.Dt = simulation_info.get("time-window-size", 1e-3)
self.sim_info.accuracy = "medium"

# Initialize coupling type to None
self.coupling_type = None

# Extract coupling type from exchanges
if 'exchanges' in etree:
exchanges = etree['exchanges']
exchange_types = [exchange.get('type') for exchange in exchanges if 'type' in exchange]

# Validate exchange types
if exchange_types:
# If all types are the same, set that as the coupling type
if len(set(exchange_types)) == 1:
if exchange_types[0] == 'strong' or exchange_types[0] == 'weak':
self.coupling_type = exchange_types[0]
else:
mylog.rep_error(f"Invalid exchange type: {exchange_types[0]}. Must be 'strong' or 'weak'.")
self.coupling_type = None
else:
# Mixed types, default to weak
#mylog.rep_error("Mixed exchange types detected. Defaulting to 'weak'.")
self.coupling_type = 'weak'

# --- Parse participants ---
self.participants = {}
participants_data = etree["participants"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
coupling-scheme:
max-time: 1e-1
relative-accuracy: 1e-4
time-window-size: 1e-3
exchanges:
- data: Force
from: Fluid
from-patch: interface
to: Solid
to-patch: surface
- data: Displacement
from: Solid
from-patch: surface
to: Fluid
to-patch: interface
participants:
Fluid: SU2
Solid: Calculix
20 changes: 20 additions & 0 deletions tests/generation-tests/topology_coupling_tests/topology_mixed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
coupling-scheme:
max-time: 1e-1
relative-accuracy: 1e-4
time-window-size: 1e-3
exchanges:
- data: Force
from: Fluid
from-patch: interface
to: Solid
to-patch: surface
type: strong
- data: Displacement
from: Solid
from-patch: surface
to: Fluid
to-patch: interface
type: weak
participants:
Fluid: SU2
Solid: Calculix
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
coupling-scheme:
max-time: 1e-1
relative-accuracy: 1e-4
time-window-size: 1e-3
exchanges:
- data: Force
from: Fluid
from-patch: interface
to: Solid
to-patch: surface
type: strong
- data: Displacement
from: Solid
from-patch: surface
to: Fluid
to-patch: interface
type: strong
participants:
Fluid: SU2
Solid: Calculix
20 changes: 20 additions & 0 deletions tests/generation-tests/topology_coupling_tests/topology_weak.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
coupling-scheme:
max-time: 1e-1
relative-accuracy: 1e-4
time-window-size: 1e-3
exchanges:
- data: Force
from: Fluid
from-patch: interface
to: Solid
to-patch: surface
type: weak
- data: Displacement
from: Solid
from-patch: surface
to: Fluid
to-patch: interface
type: weak
participants:
Fluid: SU2
Solid: Calculix
171 changes: 171 additions & 0 deletions tests/test_coupling_scheme_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import os
import shutil
from pathlib import Path
import yaml
import pytest

from FileGenerator import FileGenerator

class CouplingSchemeConfigurationTest:
def __init__(self):
self.template_config = Path('controller_utils/examples/1/topology.yaml')
self.output_dir = Path('tests/generation-tests/topology_coupling_tests')
self.output_dir.mkdir(parents=True, exist_ok=True)

def _create_temp_config(self, topology_type, exchanges=None):
# Create a unique filename for each topology type
output_filename = self.output_dir / f'topology_{topology_type}.yaml'

# Load the original topology
with open(self.template_config, 'r') as f:
topology_config = yaml.safe_load(f)

# Override exchanges if provided
if exchanges:
topology_config['exchanges'] = exchanges
else:
# Replace exchange types if not explicitly provided
for exchange in topology_config['exchanges']:
exchange['type'] = topology_type

# Save modified topology
with open(output_filename, 'w') as f:
yaml.dump(topology_config, f)

# Generate config using FileGenerator
file_generator = FileGenerator(
input_file=output_filename,
output_path=self.output_dir
)
file_generator._generate_precice_config()

# Return the generated XML config path
return self.output_dir / '_generated' / 'precice-config.xml'

def _check_coupling_type(self, config_file, expected_type):
# Read the XML content
with open(config_file, 'r') as f:
xml_content = f.read()
print(f"\nFull XML Content:\n{xml_content}")

# Find the coupling scheme line
coupling_scheme_lines = [line for line in xml_content.split('\n') if 'coupling-scheme:' in line]

if not coupling_scheme_lines:
raise ValueError("No coupling scheme line found in XML")

coupling_scheme_line = coupling_scheme_lines[0].strip()
print(f"\nCoupling Scheme Line: {coupling_scheme_line}")

# Determine coupling type based on the line
if expected_type == 'implicit':
assert 'parallel-implicit' in coupling_scheme_line or 'implicit' in coupling_scheme_line, \
f"Expected implicit coupling, got line: {coupling_scheme_line}"
else: # explicit
assert 'parallel-explicit' in coupling_scheme_line or 'explicit' in coupling_scheme_line, \
f"Expected explicit coupling, got line: {coupling_scheme_line}"

print(f"✓ Verified {expected_type} coupling for {config_file}")

def test_strong_topology_implicit_coupling():
test = CouplingSchemeConfigurationTest()
config_file = test._create_temp_config('strong')
test._check_coupling_type(config_file, 'implicit')

def test_weak_topology_explicit_coupling():
test = CouplingSchemeConfigurationTest()
config_file = test._create_temp_config('weak')
test._check_coupling_type(config_file, 'explicit')

def test_mixed_exchanges_weak_coupling():
test = CouplingSchemeConfigurationTest()
mixed_exchanges = [
{
'from': 'Fluid',
'from-patch': 'interface',
'to': 'Solid',
'to-patch': 'surface',
'data': 'Force',
'type': 'strong'
},
{
'from': 'Solid',
'from-patch': 'surface',
'to': 'Fluid',
'to-patch': 'interface',
'data': 'Displacement',
'type': 'weak'
}
]
config_file = test._create_temp_config('mixed', mixed_exchanges)
test._check_coupling_type(config_file, 'explicit')

def test_all_weak_exchanges_explicit_coupling():
test = CouplingSchemeConfigurationTest()
weak_exchanges = [
{
'from': 'Fluid',
'from-patch': 'interface',
'to': 'Solid',
'to-patch': 'surface',
'data': 'Force',
'type': 'weak'
},
{
'from': 'Solid',
'from-patch': 'surface',
'to': 'Fluid',
'to-patch': 'interface',
'data': 'Displacement',
'type': 'weak'
}
]
config_file = test._create_temp_config('weak', weak_exchanges)
test._check_coupling_type(config_file, 'explicit')

def test_all_strong_exchanges_implicit_coupling():
test = CouplingSchemeConfigurationTest()
strong_exchanges = [
{
'from': 'Fluid',
'from-patch': 'interface',
'to': 'Solid',
'to-patch': 'surface',
'data': 'Force',
'type': 'strong'
},
{
'from': 'Solid',
'from-patch': 'surface',
'to': 'Fluid',
'to-patch': 'interface',
'data': 'Displacement',
'type': 'strong'
}
]
config_file = test._create_temp_config('strong', strong_exchanges)
test._check_coupling_type(config_file, 'implicit')

def test_no_exchange_types_default_implicit_coupling():
test = CouplingSchemeConfigurationTest()
no_type_exchanges = [
{
'from': 'Fluid',
'from-patch': 'interface',
'to': 'Solid',
'to-patch': 'surface',
'data': 'Force'
},
{
'from': 'Solid',
'from-patch': 'surface',
'to': 'Fluid',
'to-patch': 'interface',
'data': 'Displacement'
}
]
config_file = test._create_temp_config('default', no_type_exchanges)
test._check_coupling_type(config_file, 'implicit')

if __name__ == '__main__':
pytest.main([__file__])

0 comments on commit 467f2b1

Please sign in to comment.