pyroll-project / pyroll-core

PyRoll rolling simulation framework - core library.
https://pyroll.readthedocs.io
BSD 3-Clause "New" or "Revised" License
12 stars 7 forks source link

Higher Power Demand with Velocity calculation #256

Open smkhalid opened 6 days ago

smkhalid commented 6 days ago

The roll pass velocity is currently being set to the rolled stock velocity like this: https://github.com/pyroll-project/pyroll-core/blob/640824afb80508d49edb6f65bd7b0831116ec4b6/pyroll/core/sequence/sequence.py#L138-L140

Problem

Total Power demand is much higher when calculating Roll Pass velocity using the Velocity calculation function #247. I have tested the function for Three roll pass (flat groove) and Two roll pass (box groove) systems. I had the same problem in both cases.

Proposed Solution

Instead of replacing the roll pass velocity with rolled stock velocity, consider converting the rolled stock velocity to the rotational frequency of a specific roll pass and then replacing the Roll's rotational frequency. This approach can be tested. In my case, I was able to get the expected power demand.

for roll_pass, velocity in zip(self.roll_passes, initial_velocities):
    roll_pass.roll.rotational_frequency = velocity/(2*np.pi*roll_pass.roll.nominal_radius)
ChRen95 commented 5 days ago

Please provide a minimal working example that recreates your problem (https://stackoverflow.com/help/minimal-reproducible-example)

smkhalid commented 2 days ago

Here is a minimal working example of the described issue:

In this particular case while I am using the default function: Total Power required: 4279269.22774837 while replacing velocity with rotational frequency: Total Power required: 681066.8504171907

Environment: Python Version: 3.12 Pyroll Core: 2.1.6 IDE: PyCharm with Jupyter Notebook


# import Libraries
import copy
import numpy as np
import pyroll.core as pr
import pyroll.freiberg_flow_stress
import pyroll.lendl_equivalent_method
import pyroll.sparling_spreading
import pyroll.hensel_power_and_labour
from pyroll.freiberg_flow_stress import FreibergFlowStressCoefficients

# Creating incoming Profile
ip = pr.Profile.box(
    height=160e-3,
    width=160e-3,
    corner_radius=3e-3,
    temperature=1100 + 273.15,
    strain=0,
    material=["BST500", "steel"],
    freiberg_flow_stress_coefficients=FreibergFlowStressCoefficients(
        a=4877.12 * 1e6,
        m1=-0.00273339,
        m2=0.302309,
        m3=-0.0407581,
        m4=0.000222222,
        m5=-0.000383134,
        m6=0,
        m7=-0.492672,
        m8=0.000175044,
        m9=-0.0611783,
        baseStrain=0.1,
        baseStrainRate=0.1
    ),
    density=7.5e3,
    specific_heat_capacity=690,
    thermal_conductivity=23,
)

# Pass Sequence 
demo_pass_sequence = pr.PassSequence([
    pr.RollPass(
        label="Stand A - New",
        orientation="h",
        roll=pr.Roll(
            groove=pr.BoxGroove(
                usable_width=185.29e-3,
                depth=48e-3,
                ground_width=157.62e-3,
                r1=15e-3,
                r2=18e-3
            ),
            nominal_radius=635e-3 / 2,
            temperature=20 + 273.15
        ),
        gap=15e-3,
        sparling_material_coefficient=0.95,
    ),
    pr.Transport(
        label="A => B",
        length=2.5
    ),
    pr.RollPass(
        label="Stand B - New",
        orientation="v",
        roll=pr.Roll(
            groove=pr.BoxGroove(
                usable_width=138.435e-3,
                depth=49.5e-3,
                ground_width=108.693e-3,
                r1=15e-3,
                r2=20e-3
            ),
            nominal_radius=635e-3 / 2,
            temperature=20 + 273.15
        ),
        gap=20e-3,
        sparling_material_coefficient=0.95,
    )
])

class VelocityCalculator:
    def __init__(self, in_profile: pr.Profile, pass_sequence: pr.PassSequence, ):

        self.in_profile = in_profile
        self.pass_sequence = pass_sequence
        self.roll_passes = self.pass_sequence.roll_passes
        self.usable_cross_section_areas = np.asarray(
            [roll_pass.usable_cross_section.area for roll_pass in self.roll_passes])
        self.initial_velocities = np.zeros_like(self.usable_cross_section_areas, dtype=float)
        self.max_iterations = 100

    def solve_backward(self, finishing_cross_section_area: float, finishing_speed: float):
        initial_velocities = self.initial_velocities.copy()
        usable_cross_section_areas = self.usable_cross_section_areas.copy()

        initial_velocities[-1] = finishing_speed
        usable_cross_section_areas[-1] = finishing_cross_section_area

        for i in range(len(usable_cross_section_areas) - 2, -1, -1):
            initial_velocities[i] = initial_velocities[i + 1] * usable_cross_section_areas[i + 1] / \
                                    usable_cross_section_areas[i]

        for roll_pass, velocity in zip(self.roll_passes, initial_velocities):
            roll_pass.roll.rotational_frequency = velocity/(2*np.pi*roll_pass.roll.nominal_radius)

        self.pass_sequence.solve(self.in_profile)

        for i in range(self.max_iterations):

            prior_velocities = np.asarray([roll_pass.velocity for roll_pass in self.roll_passes])
            current_velocities = prior_velocities.copy()
            profile_areas = [roll_pass.out_profile.cross_section.area for roll_pass in self.roll_passes]

            for j in range(len(profile_areas) - 2, -1, -1):
                current_velocities[j] = current_velocities[j + 1] * profile_areas[j + 1] / profile_areas[j]

            for roll_pass, velocity in zip(self.roll_passes, current_velocities):
                roll_pass.roll.rotational_frequency = velocity/(2*np.pi*roll_pass.roll.nominal_radius)

            self.pass_sequence.solve(self.in_profile)

            difference = np.abs(prior_velocities - current_velocities)

            if np.all(difference < 0.01):
                return self.pass_sequence

# calculating velocity backward based on finish speed
pass_sequence_with_velocities = VelocityCalculator(pass_sequence=demo_pass_sequence, in_profile=ip).solve_backward(
    finishing_speed=0.5, finishing_cross_section_area=(110e-3 * 12e-3))

# Total Power
print("Total Power required: ",  np.sum([rp.power for rp in pass_sequence_with_velocities.roll_passes]))