diff --git a/pyroll/neutral_point_estimator/__init__.py b/pyroll/neutral_point_estimator/__init__.py index 1fa74d3..208c1e5 100644 --- a/pyroll/neutral_point_estimator/__init__.py +++ b/pyroll/neutral_point_estimator/__init__.py @@ -15,4 +15,5 @@ class Config: from . import sims_neutral_point_estimator from . import equal_neutral_line_estimator from . import lichansky_polyakov_ribbed_profile_neutral_point -from . import napatov_ribbed_profile_neutral_point \ No newline at end of file +from . import napatov_ribbed_profile_neutral_point +from . import lippmann_mahrenholz \ No newline at end of file diff --git a/pyroll/neutral_point_estimator/lippmann_mahrenholz.py b/pyroll/neutral_point_estimator/lippmann_mahrenholz.py new file mode 100644 index 0000000..549d2eb --- /dev/null +++ b/pyroll/neutral_point_estimator/lippmann_mahrenholz.py @@ -0,0 +1,29 @@ +import numpy as np + +from . import Config +from .utils import chosen_estimator +from pyroll.core import SymmetricRollPass, Hook + +SymmetricRollPass.Roll.relative_neutral_angle = Hook[float]() +"""Relative Neutral angle defined by Lippmann and Mahrenholz for the roll pass.""" + +@SymmetricRollPass.Roll.relative_neutral_angle +def relative_neutral_angle(self: SymmetricRollPass.Roll): + rp = self.roll_pass + mean_flow_stress = (rp.in_profile.flow_stress + 2 * rp.out_profile.flow_stress) / 3 + abs_rel_draught = abs(rp.rel_draught) + back_tension_model_definition = -rp.back_tension + front_tension_model_definition = -rp.front_tension + relative_tension = (back_tension_model_definition - front_tension_model_definition) / mean_flow_stress + + p1 = np.sqrt((1 - abs_rel_draught) / abs_rel_draught) + p2 = 1 / 2 * np.sqrt(rp.out_profile.equivalent_height / self.working_radius) * (relative_tension + np.log(1 - abs_rel_draught)) + p3 = 1 / 2 * np.arctan(np.sqrt(abs_rel_draught / (1 - abs_rel_draught))) + + return p1 * np.tan(p2 + p3) + + +@SymmetricRollPass.Roll.neutral_angle +def neutral_angle(self: SymmetricRollPass.Roll): + if chosen_estimator(Config.ESTIMATOR, "lippmann-mahrenholz"): + return self.entry_angle * self.relative_neutral_angle diff --git a/test/test_solve_lippmann_mahrenholz.py b/test/test_solve_lippmann_mahrenholz.py new file mode 100644 index 0000000..ddae046 --- /dev/null +++ b/test/test_solve_lippmann_mahrenholz.py @@ -0,0 +1,89 @@ +import logging +import webbrowser +from pathlib import Path + +from pyroll.core import Profile, PassSequence, RollPass, Roll, CircularOvalGroove, Transport, RoundGroove, root_hooks + + +def test_solve_lippmann_mahrenholz(tmp_path: Path, caplog, monkeypatch): + caplog.set_level(logging.INFO, logger="pyroll") + + import pyroll.neutral_point_estimator + monkeypatch.setenv("PYROLL_NEUTRAL_POINT_ESTIMATOR_ESTIMATOR", "LIPPMANN-MAHRENHOLZ") + + root_hooks.add(Roll.neutral_point) + + try: + in_profile = Profile.round( + diameter=30e-3, + temperature=1200 + 273.15, + strain=0, + material=["C45", "steel"], + flow_stress=100e6, + density=7.5e3, + thermal_capacity=690, + ) + + sequence = PassSequence([ + RollPass( + label="Oval I", + roll=Roll( + groove=CircularOvalGroove( + depth=8e-3, + r1=6e-3, + r2=40e-3 + ), + nominal_radius=160e-3, + rotational_frequency=1 + ), + gap=2e-3, + coulomb_friction_coefficient=0.4, + back_tension=0, + front_tension=5e6 + ), + Transport( + label="I => II", + duration=1 + ), + RollPass( + label="Round II", + roll=Roll( + groove=RoundGroove( + r1=1e-3, + r2=12.5e-3, + depth=11.5e-3 + ), + nominal_radius=160e-3, + rotational_frequency=1 + ), + gap=2e-3, + coulomb_friction_coefficient=0.4, + back_tension=5e6, + front_tension=0 + ), + ]) + + try: + sequence.solve(in_profile) + + assert sequence[0].roll.neutral_point < 0 + assert sequence[2].roll.neutral_point < 0 + + finally: + print("\nLog:") + print(caplog.text) + finally: + root_hooks.remove(Roll.neutral_point) + + try: + import pyroll.report + + report = pyroll.report.report(sequence) + + report_file = tmp_path / "report.html" + report_file.write_text(report, encoding="utf-8") + print(report_file) + webbrowser.open(report_file.as_uri()) + + except ImportError: + pass diff --git a/test/test_solve_lippmann_mahrenholz3.py b/test/test_solve_lippmann_mahrenholz3.py new file mode 100644 index 0000000..4cf0603 --- /dev/null +++ b/test/test_solve_lippmann_mahrenholz3.py @@ -0,0 +1,89 @@ +import logging +import webbrowser +from pathlib import Path + +from pyroll.core import Profile, Roll, ThreeRollPass, Transport, RoundGroove, CircularOvalGroove, PassSequence, root_hooks + + +def test_solve_lippmann_mahrenholz3(tmp_path: Path, caplog, monkeypatch): + caplog.set_level(logging.DEBUG, logger="pyroll") + + import pyroll.neutral_point_estimator + monkeypatch.setenv("PYROLL_NEUTRAL_POINT_ESTIMATOR_ESTIMATOR", "LIPPMANN-MAHRENHOLZ") + + root_hooks.add(Roll.neutral_point) + + in_profile = Profile.round( + diameter=55e-3, + temperature=1200 + 273.15, + strain=0, + material=["C45", "steel"], + flow_stress=100e6, + density=7.5e3, + thermal_capacity=690, + ) + + sequence = PassSequence([ + ThreeRollPass( + label="Oval I", + roll=Roll( + groove=CircularOvalGroove( + depth=8e-3, + r1=6e-3, + r2=40e-3, + pad_angle=30 + ), + nominal_radius=160e-3, + rotational_frequency=1 + ), + gap=2e-3, + coulomb_friction_coefficient=0.4, + back_tension=0, + front_tension=5e6 + ), + Transport( + label="I => II", + duration=1 + ), + ThreeRollPass( + label="Round II", + roll=Roll( + groove=RoundGroove( + r1=3e-3, + r2=25e-3, + depth=11e-3, + pad_angle=30 + ), + nominal_radius=160e-3, + rotational_frequency=1 + ), + gap=2e-3, + coulomb_friction_coefficient=0.4, + back_tension=5e6, + front_tension=0 + ), + ]) + + try: + sequence.solve(in_profile) + + assert sequence[0].roll.neutral_point < 0 + assert sequence[2].roll.neutral_point < 0 + finally: + root_hooks.remove(Roll.neutral_point) + + print("\nLog:") + print(caplog.text) + + try: + import pyroll.report + + report = pyroll.report.report(sequence) + + report_file = tmp_path / "report.html" + report_file.write_text(report, encoding="utf-8") + print(report_file) + webbrowser.open(report_file.as_uri()) + + except ImportError: + pass