-
Notifications
You must be signed in to change notification settings - Fork 84
RO Scaling improvements #1704
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
base: main
Are you sure you want to change the base?
RO Scaling improvements #1704
Conversation
|
|
||
| def calculate_scaling_factors(self): | ||
| if iscale.get_scaling_factor(self.area) is None: | ||
| iscale.set_scaling_factor(self.area, 100) |
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.
Should we also display a warning to the user that we are doing this since so scaling factor was provided?
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.
Do the old scaling tools really need additional warnings to spam the user's logs?
In the new scaling tools, I'm going to raise an exception instead, so they can no longer ignore it.
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.
@dallan-keylogic in the new scaling tools, could you think about perhaps a setting where the user could choose to have exceptions raised or bypass (with warnings if you prefer)? The default would be to raise exceptions, but it'd be nice to have an escape route for devs (for whatever exploratory reasons someone might have).
| if iscale.get_scaling_factor(v) is None: | ||
| iscale.set_scaling_factor(v, 1) | ||
|
|
||
| # TODO why did this file have zero scaling factors for constraints |
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.
@bknueven can probably give a more informative response, but the line of thinking was that scaling all variables should suffice in most cases without the need to scale constraints. However, we know that this isn't always the case, as we've experienced particularly when dealing with getting "complex" models to converge.
| warnings, next_steps = dt._collect_numerical_warnings() | ||
| assert len(warnings) == 3 | ||
| warnings, _ = dt._collect_numerical_warnings() | ||
| assert len(warnings) == 1 |
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.
nice
|
|
||
| initializer = BlockTriangularizationInitializer() | ||
| initializer = BlockTriangularizationInitializer( | ||
| calculate_variable_options={"eps": 2e-8}, skip_final_solve=True |
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.
how is eps applied through calculate_variable_options?
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.
It is the tolerance used to check whether the constraint residual is small enough to consider the constraint "satisfied". When solving the flowsheet, I encountered a problem where it couldn't reduce the residual of some enthalpy constraint (i.e., a constraint that needs a scaling factor <<1) to less than 1e-8. Due to Pyomo/pyomo#3785 , it does not take the constraint's scaling factor into account when calculating this residual. Using a constraint_scaling_transformation would allow scaling to be taken into account, but we wanted to move away from that in the new scaling tools.
| # TODO is it intentional that we're setting scaling factors for some constraints | ||
| # but doing constraint scaling transforms for others? |
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.
Not intentional--traditionally we applied constraint scaling transforms.
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.
this appears to contradict @MarcusHolly in #1695
| b.flow_mass_phase_comp[p, j] / b.params.mw_comp[j] | ||
| == b.flow_mol_phase_comp[p, j] |
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.
Why opt for this form?
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.
This is changed due to how the WaterTap scaling utilities work. Because the constraint is named eq_flow_mol_phase_comp, it is automatically scaled using the scaling factor for flow_mol_phase_comp. However, as originally written, it ought to be scaled instead like flow_mass_phase_comp. This slight modification of the constraint is easier than messing with that workflow.
| ] == pytest.approx(12120000, rel=1e-3) | ||
| ] == pytest.approx(4.309e07, rel=1e-3) | ||
| assert m.fs.stream[0].scaling_factor[ | ||
| m.fs.stream[0].eq_elec_mobility_phase_comp["Liq", "Na_+"] | ||
| ] == pytest.approx(19320000, rel=1e-3) | ||
| ] == pytest.approx(8.617e07, rel=1e-3) | ||
| assert m.fs.stream[0].scaling_factor[ | ||
| m.fs.stream[0].eq_elec_mobility_phase_comp["Liq", "Cl_-"] | ||
| ] == pytest.approx(12660000, rel=1e-3) | ||
| ] == pytest.approx(8.617e07, rel=1e-3) | ||
| assert m.fs.stream[0].scaling_factor[ | ||
| m.fs.stream[0].eq_elec_mobility_phase_comp["Liq", "Mg_2+"] | ||
| ] == pytest.approx(18200000, rel=1e-3) | ||
| ] == pytest.approx(4.309e07, rel=1e-3) |
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.
Unclear why we are getting relatively significant changes in these test values
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.
It's clearer now.
| ] == pytest.approx(0.01866, rel=1e-3) | ||
| ] == pytest.approx(0.1, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Ca_2+"] | ||
| ] == pytest.approx(104.7, rel=1e-3) | ||
| ] == pytest.approx(1e3, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "SO4_2-"] | ||
| ] == pytest.approx(44.94, rel=1e-3) | ||
| ] == pytest.approx(1e2, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Mg_2+"] | ||
| ] == pytest.approx(1.722, rel=1e-3) | ||
| ] == pytest.approx(10, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Na_+"] | ||
| ] == pytest.approx(2.068, rel=1e-3) | ||
| ] == pytest.approx(10, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Cl_-"] | ||
| ] == pytest.approx(0.6174, rel=1e-3) | ||
| ] == pytest.approx(1, rel=1e-3) | ||
|
|
||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "H2O"] | ||
| ] == pytest.approx(0.0233193, rel=1e-5) | ||
| ] == pytest.approx(0.1, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "Ca_2+"] | ||
| ] == pytest.approx(0.004, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "SO4_2-"] | ||
| ] == pytest.approx(0.00960, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "Mg_2+"] | ||
| ] == pytest.approx(0.00240, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "Na_+"] | ||
| ] == pytest.approx(0.0023, rel=1e-3) | ||
|
|
||
| assert ( | ||
| mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.permeate_electronegativity[0.0] | ||
| ] | ||
| == 1 | ||
| ) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.retentate_pressure_balance[0.0] | ||
| ] == pytest.approx(0.0000025, rel=1e-3) | ||
| ] == pytest.approx(1e-5, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.retentate_temperature_equality[0.0] | ||
| ] == pytest.approx(0.003354, rel=1e-3) | ||
| ] == pytest.approx(1e-2, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.permeate_temperature_equality[0.0] | ||
| ] == pytest.approx(0.003354, rel=1e-3) | ||
| ] == pytest.approx(1e-2, rel=1e-3) |
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.
These changes seem significant
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.
These changes weren't a result of this PR, but instead #1695. They're due to a change in how the nominal value expression walker works. Previously it used 1/value(var) if var was fixed, but now it uses 1/scaling_factor[var] regardless of whether or not var is fixed.
| self.skip_badly_scaled_vars = True | ||
| self.condition_number = 3.9140627e8 # Was previously 4.8157167e15 |
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.
these seem like great improvements!
| b.flow_mass_phase_comp[p, j] / b.params.mw_comp[j] | ||
| == b.flow_mol_phase_comp[p, j] |
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.
This is changed due to how the WaterTap scaling utilities work. Because the constraint is named eq_flow_mol_phase_comp, it is automatically scaled using the scaling factor for flow_mol_phase_comp. However, as originally written, it ought to be scaled instead like flow_mass_phase_comp. This slight modification of the constraint is easier than messing with that workflow.
|
|
||
| def calculate_scaling_factors(self): | ||
| if iscale.get_scaling_factor(self.area) is None: | ||
| iscale.set_scaling_factor(self.area, 100) |
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.
Do the old scaling tools really need additional warnings to spam the user's logs?
In the new scaling tools, I'm going to raise an exception instead, so they can no longer ignore it.
|
|
||
| initializer = BlockTriangularizationInitializer() | ||
| initializer = BlockTriangularizationInitializer( | ||
| calculate_variable_options={"eps": 2e-8}, skip_final_solve=True |
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.
It is the tolerance used to check whether the constraint residual is small enough to consider the constraint "satisfied". When solving the flowsheet, I encountered a problem where it couldn't reduce the residual of some enthalpy constraint (i.e., a constraint that needs a scaling factor <<1) to less than 1e-8. Due to Pyomo/pyomo#3785 , it does not take the constraint's scaling factor into account when calculating this residual. Using a constraint_scaling_transformation would allow scaling to be taken into account, but we wanted to move away from that in the new scaling tools.
| b.flow_mass_phase_comp[p, j] / b.params.mw_comp[j] | ||
| == b.flow_mol_phase_comp[p, j] |
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.
This is changed due to how the WaterTap scaling utilities work. Because the constraint is named eq_flow_mol_phase_comp, it is automatically scaled using the scaling factor for flow_mol_phase_comp. However, as originally written, it ought to be scaled instead like flow_mass_phase_comp. This slight modification of the constraint is easier than messing with that workflow.
| ] == pytest.approx(0.01866, rel=1e-3) | ||
| ] == pytest.approx(0.1, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Ca_2+"] | ||
| ] == pytest.approx(104.7, rel=1e-3) | ||
| ] == pytest.approx(1e3, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "SO4_2-"] | ||
| ] == pytest.approx(44.94, rel=1e-3) | ||
| ] == pytest.approx(1e2, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Mg_2+"] | ||
| ] == pytest.approx(1.722, rel=1e-3) | ||
| ] == pytest.approx(10, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Na_+"] | ||
| ] == pytest.approx(2.068, rel=1e-3) | ||
| ] == pytest.approx(10, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.material_balances[0.0, "Cl_-"] | ||
| ] == pytest.approx(0.6174, rel=1e-3) | ||
| ] == pytest.approx(1, rel=1e-3) | ||
|
|
||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "H2O"] | ||
| ] == pytest.approx(0.0233193, rel=1e-5) | ||
| ] == pytest.approx(0.1, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "Ca_2+"] | ||
| ] == pytest.approx(0.004, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "SO4_2-"] | ||
| ] == pytest.approx(0.00960, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "Mg_2+"] | ||
| ] == pytest.approx(0.00240, rel=1e-5) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.separation_constraint[0.0, "Liq", "Na_+"] | ||
| ] == pytest.approx(0.0023, rel=1e-3) | ||
|
|
||
| assert ( | ||
| mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.permeate_electronegativity[0.0] | ||
| ] | ||
| == 1 | ||
| ) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.retentate_pressure_balance[0.0] | ||
| ] == pytest.approx(0.0000025, rel=1e-3) | ||
| ] == pytest.approx(1e-5, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.retentate_temperature_equality[0.0] | ||
| ] == pytest.approx(0.003354, rel=1e-3) | ||
| ] == pytest.approx(1e-2, rel=1e-3) | ||
| assert mcas_case.fs.unit.scaling_factor[ | ||
| mcas_case.fs.unit.permeate_temperature_equality[0.0] | ||
| ] == pytest.approx(0.003354, rel=1e-3) | ||
| ] == pytest.approx(1e-2, rel=1e-3) |
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.
These changes weren't a result of this PR, but instead #1695. They're due to a change in how the nominal value expression walker works. Previously it used 1/value(var) if var was fixed, but now it uses 1/scaling_factor[var] regardless of whether or not var is fixed.
Summary/Motivation:
Add additional scaling for various unit models, mostly involving RO. Depends on #1695.
Also, the unit test harness is slightly modified. The check for "badly scaled variables" can be bypassed, and an optional check for the model condition number is added instead.
Legal Acknowledgement
By contributing to this software project, I agree to the following terms and conditions for my contribution: