-
Notifications
You must be signed in to change notification settings - Fork 10
refactor: introduce FeasibilitySolver and StreamDistributionItem #1457
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
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a3405bb
chore: init mapping
jsolaas 192bf1e
refactor: implement has validtiy and capacity
frodehk aed8fdd
test: update test
frodehk b779bbb
chore: set speed explicitly
frodehk 9f5b465
docs: update docstring
frodehk ee5465e
chore: update comment
frodehk c248254
chore: implement feasibility solver
frodehk 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
73 changes: 73 additions & 0 deletions
73
src/libecalc/domain/process/process_solver/feasibility_solver.py
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 |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| from libecalc.domain.process.entities.process_units.compressor import Compressor | ||
| from libecalc.domain.process.process_solver.float_constraint import FloatConstraint | ||
| from libecalc.domain.process.process_solver.outlet_pressure_solver import OutletPressureSolver | ||
| from libecalc.domain.process.process_solver.process_runner import ProcessRunner | ||
| from libecalc.domain.process.value_objects.fluid_stream import FluidStream | ||
|
|
||
|
|
||
| class FeasibilitySolver: | ||
| """ | ||
| Calculates how much of a given inlet rate exceeds what a compressor train | ||
| can handle for a target pressure. | ||
|
|
||
| Orchestrates OutletPressureSolver and queries compressor charts to find | ||
| the feasible rate — the excess is redirected via stream distribution. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| outlet_pressure_solver: OutletPressureSolver, | ||
| compressors: list[Compressor], | ||
| runner: ProcessRunner, | ||
| ): | ||
| self._solver = outlet_pressure_solver | ||
| self._compressors = compressors | ||
| self._runner = runner | ||
|
|
||
| def get_excess_rate( | ||
| self, | ||
| inlet_stream: FluidStream, | ||
| target_pressure: FloatConstraint, | ||
| ) -> float: | ||
| """ | ||
| Rate [sm³/day] that exceeds what this train can handle. | ||
|
|
||
| This is the amount that must be redirected (e.g. via overflow) | ||
| to another train in the stream distribution. | ||
| """ | ||
| feasible = self._find_feasible_rate(inlet_stream, target_pressure) | ||
| excess_rate = max(0.0, inlet_stream.standard_rate_sm3_per_day - feasible) | ||
| return excess_rate | ||
|
|
||
| def _find_feasible_rate( | ||
| self, | ||
| inlet_stream: FluidStream, | ||
| target_pressure: FloatConstraint, | ||
| ) -> float: | ||
| """Highest standard rate [sm³/day] for which the train can meet target_pressure. | ||
|
|
||
| Returns the full inlet rate if the solver succeeds, otherwise finds the | ||
| bottleneck compressor's stone wall limit at the current operating point. | ||
| """ | ||
| solution = self._solver.find_solution(target_pressure, inlet_stream) | ||
|
|
||
| if solution.success: | ||
| # The train can handle the full rate — no need to search for a bottleneck. | ||
| return inlet_stream.standard_rate_sm3_per_day | ||
|
|
||
| # Apply the configuration before querying compressor charts. | ||
| self._runner.apply_configurations(solution.configuration) | ||
|
|
||
| # Search for compressor with the lowest max rate | ||
| min_max_rate = float("inf") | ||
| for compressor in self._compressors: | ||
| compressor_inlet = self._runner.run( | ||
| inlet_stream=inlet_stream, | ||
| to_id=compressor.get_id(), | ||
| ) | ||
| max_rate = compressor.get_maximum_standard_rate(compressor_inlet) | ||
| min_max_rate = min(min_max_rate, max_rate) | ||
|
|
||
| feasible_rate = max(0.0, min_max_rate) | ||
|
|
||
| return feasible_rate |
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
27 changes: 27 additions & 0 deletions
27
src/libecalc/presentation/yaml/mappers/process_simulation_mapper.py
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 |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from libecalc.domain.process.process_solver.feasibility_solver import FeasibilitySolver | ||
| from libecalc.domain.process.process_solver.float_constraint import FloatConstraint | ||
| from libecalc.domain.process.stream_distribution.common_stream_distribution import HasExcessRate | ||
| from libecalc.domain.process.stream_distribution.priorities_stream_distribution import HasValidity | ||
| from libecalc.domain.process.value_objects.fluid_stream import FluidStream | ||
|
|
||
|
|
||
| class StreamDistributionItem(HasExcessRate, HasValidity): | ||
| """Connects a compressor train's solver to the stream distribution system.""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| feasibility_solver: FeasibilitySolver, | ||
| target_pressure: FloatConstraint, | ||
| ): | ||
| self._feasibility_solver = feasibility_solver | ||
| self._target_pressure = target_pressure | ||
|
|
||
| def is_valid(self, inlet_stream: FluidStream) -> bool: | ||
| """Can the train operate at these inlet conditions?""" | ||
| return self.get_excess_rate(inlet_stream) == 0.0 | ||
|
|
||
| def get_excess_rate(self, inlet_stream: FluidStream) -> float: | ||
| """How much rate (sm³/day) exceeds this train's capacity?""" | ||
| return self._feasibility_solver.get_excess_rate( | ||
| inlet_stream=inlet_stream, target_pressure=self._target_pressure | ||
| ) |
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.
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.
I was thinking CommonStreamDistribution would use FeasibilitySolver directly, so passing target_pressure in addition. Not sure if that matters much, but it would remove the HasExcessRate interface which might be a bit confusing. It's unclear that excess rate depends on target pressure in that interface.
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.
I see your point about clarity. However, if we remove the
StreamDistributionItem(HasValidity,HasExcessCapacity),CommonStreamDistributionneeds to keep track of two dictionaries (feasibility solvers, target pressures) - and I guess tests will be "heavier" (need to buildOutletPressureSolver,ProcessRunner, compressors etc.). Another thing is thatPrioritiesStreamDistributionis usingHasValidity. WithoutStreamDistributionItemI assumePrioritiesStreamDistributionalso needs to take feasibility solvers and target pressures.Can it be an alternative to have a clearer docstring in the
HasExcessRateinterface, to clarify the target pressure dependency?