Categories
C#

Moment bezwładności przekroju niezarysowanego

Wpis [part not set] z 8 w serii C# - podstawy

Poznaliśmy koncept klas i obiektów, udało nam się dotychczas wyróżnić dwie klasy: ConcreteSection oraz Rebar. Jednak poza definicją klasy Rebar nie zaliczyliśmy znacznego postępu. Naszym celem jest stworzenie programu wyznaczającego moment bezwładności prostokątnego przekroju niezarysowanego. Podstawy teoretyczne pochodzą ze strony https://chodor-projekt.net/ – ogromne podziękowania dla Pana Leszka Chodora za dzielenie się wiedzą!

Nasze zadanie zahacza o pojęcie ‘sprowadzonego pola przekroju’. Mamy betonowy przekrój prostokątny w którym znajdują się pręty stalowe, wykonane z innego materiału, posiadające znacznie większy moduł spreżystości niż otaczający je beton. Faza niezarysowana jest o tyle przyjemna, że możemy przymknąc oko na mikroświat i przyjąć w trakcie obliczeń, że miejsce zajmowane przez stal jest odpowiednio sztywniejsze od betonu, do którego chcemy ją sprowadzić. W tym celu będziemy potrzebować współczynnika ‘zamieniającego’ stal w beton.

    \[ \alpha_e=\frac{E_s}{E_{c,eff}}\]

Powyższa czarodziejska różdżka potrzebuje do działania modułu sprężystości stali i efektywnego modułu sprężystości betonu. Hmm, czyli nie wystarczy nam wiedza jaką nazwę nosi zastosowana klasa betonu i stali – potrzebujemy większej liczby właściwości opisujących te ‘byty’. Moment, w którym okazuje się, że do opisania danej cechy nie wystarczy jedna liczba lub słowo to czas na wydzielenie nowej klasy.

Tylko zanim się rozpędzimy wykonajmy jeszcze jedną czynność porządkową – utwórzmy nowy projekt, który będzie biblioteką klas, czyli czymś, czego nie da się samodzielnie uruchomić poprzez plik .exe ale będzie w przyszłości stanowić część większych programów. Z poziomu menu File wybierz Add -> New Project a następnie wskaż nowy typ projektu Class Library (.NET Framework). Nazwijmy go ConcreteLibrary.

Utworzenie nowego projektu i przekazanie mu odpowiedzialności za nową, samodzielną funkcjonalność jest ważną czynnością porządkową – w naszym wypadku umożliwi to sprawne wykorzystanie algorytmu w połączeniu z programami budowlanymi.

W ramach projektu ConcreteLibrary utwórz nowe klasy materiałowe: Steel oraz Concrete. Na użytek tego zadania proponuję coś na kształt:

    public class Steel
    {
        public string Name { get; set; }
        public double ElasticModulus { get; set; }

        public Steel(string name, double elasticModulus)
        {
            Name = name;
            ElasticModulus = elasticModulus;
        }
    }
    public class Concrete
    {
        public string Name { get; set; }
        public double ElasticModulus { get; set; }

        public Concrete(string name, double elasticModulus)
        {
            Name = name;
            ElasticModulus = elasticModulus;
        }

        public double CalculateEffectiveElasticModulus(double creepCoefficient)
        {
            return ElasticModulus / (1 + creepCoefficient);
        }
    }

Klasa Concrete jest w stanie obliczyć wartość efektywnego modułu sprężystości, uwzględniając wskazany współczynnik pełzania.

Mając tak zdefiniowane podstawowe klasy materiałowe musimy zmodyfikować dotychczasowe ConcreteSection oraz Rebar tak, żeby korzystały z dobrodziejstw nowych klas (początkowo przechowywaliśy tylko nazwy użytych materiałów):

    public class ConcreteSection
    {
        public double Width { get; set; }
        public double Height { get; set; }
        public Concrete ConcreteClass { get; set; }

        public ConcreteSection(double width, double height, Concrete concreteClass)
        {
            Width = width;
            Height = height;
            ConcreteClass = concreteClass;
        }

        public double CalculateArea()
        {
            return Width * Height;
        }

        public double CalculateFirstMomentOfArea()
        {
            return CalculateArea() * Height / 2.0;
        }

        public double CalculateMomentOfInertia()
        {
            return (Width * Math.Pow(Height, 3)) / 12;
        }
    }

Klasa Rebar zaprezentowana poniżej zyskała dodatkową moc, potrafi teraz wyznaczyć odległość do górnej krawędzi przekroju. Konstruktor klasy oczekuje podania odległości od dolnej krawędzi, z kolei żelbetowe algorytmy wykorzystują czasami domiar od góry.

public class Rebar
{
    public double Diameter { get; set; }
    public Steel SteelClass { get; set; }
    public double DistanceFromBottomEdge { get; set; }

    public Rebar(double diameter, Steel steelClass, double distanceFromBottomEdge)
    {
        Diameter = diameter;
        SteelClass = steelClass;
        DistanceFromBottomEdge = distanceFromBottomEdge;
    }

    public double CalculateArea()
    {
        return Math.PI * Diameter * Diameter / 4.0;
    }

    public double CalculateDistanceFromTopEdge(double sectionHeight)
    {
        return sectionHeight - DistanceFromBottomEdge;
    }
}

Przekrój żelbetowy składa się z wielu prętów – obiektów klasy Rebar. Musimy zastanowić się w jaki sposób będziemy przechowywać o nich informacje. Istnieją dwie podstawowe możliwości:

  • zbierać pręty w ramach klasy ReinforcedSection, która będzie również finalnie obliczać moment bezwładności,
  • stworzyć nową klasę, której głównym zadaniem będzie agregowanie informacji o prętach w przekroju.

Innymi słowami: w przyszłości wielokrotnie będziesz stawał przed dylematem gdzie umiejscawiać nowe funkcjonalności – upychać je w istniejącej strukturze klas czy zatrzymać się na chwilę, wydzielić nowe klasy oraz powiązania pomiędzy nimi. Jeżeli goni Cię deadline, to zdecydujesz się na zwiększanie już posiadanych klas – będzie szybciej. Ucierpi na tym zarządzalność projektem w przyszłości – trudniej będzie dodać nowości, trudniej będzie zrozumieć powody błędnego działania. Ale … dostarczyłeś nową funkcjonalność na czas – ot, klasyczny trade-off programisty.

Mamy czas, stwórzmy zatem nową klasę: RebarsInSection:

public class RebarsInSection : IEnumerable<Rebar>
    {
        private List<Rebar> _rebars = new List<Rebar>();

        public Rebar this[int index]
        {
            get { return _rebars[index]; }
            set { _rebars.Insert(index, value); }
        }

        public Steel GetSteelClass()
        {
            if (_rebars.Count > 0)
                return _rebars.First().SteelClass;
            else
                return null;
        }

        public void AddRebar(Rebar rebar)
        {
            _rebars.Add(rebar);
        }

        public double CalculateArea()
        {
            return _rebars.Sum(x => x.CalculateArea());
        }

        public IEnumerator<Rebar> GetEnumerator()
        {
            return _rebars.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _rebars.GetEnumerator();
        }
    }

Powyższa klasa zawiera zupełnie nowy element – po podaniu jej nazwy wpisałem : IEnumerable<Rebar>. To deklaracja implementacji interfejsu, czyli rodzaju umowy pomiędzy róznymi klasami gwarantujących sprawną współpracę. IEnumerable to podstawowy interfejs umożliwiający wyliczanie zgrupowanych obiektów – w naszym wypadku prętów. Metody GetEnumerator() musieliśmy zaimplementować w związku z wykorzystaniem tego interfejsu – niewielki koszt biorąc pod uwagę płynącego z tego zyski, o nich za chwilę.

Klasa RebarsInSection zawiera pole _rebar będące listą prętów. To w niej będziemy przechowywać pręty w przekroju. Dodatkowo mamy tu kilka metod pomocniczych, które przydadzą się w naszej finalnej klasie: ReinforcedSection, która połączy wszystkie fragmenty układanki.

    public class ReinforcedSection
    {
        private ConcreteSection _concreteSection;
        private RebarsInSection _rebars;

        public ReinforcedSection(ConcreteSection concreteSection, RebarsInSection rebars)
        {
            _concreteSection = concreteSection;
            _rebars = rebars;
        }

        public double CalculateMomentOfInertia()
        {
            double cogFromTopEdge = CalculateCenterOfGravityFromTopEdge();
            double steelToConcreteCoefficient = CalculateSteelToConcreteCoefficient();

            double inertia = _concreteSection.CalculateMomentOfInertia();
            inertia += _concreteSection.CalculateArea() * Math.Pow(_concreteSection.Height / 2 - cogFromTopEdge, 2);

            foreach (Rebar rebar in _rebars)
            {
                double distanceToCog = rebar.CalculateDistanceFromTopEdge(_concreteSection.Height) - cogFromTopEdge;
                inertia += rebar.CalculateArea() * steelToConcreteCoefficient * Math.Pow(distanceToCog, 2);
            }

            return inertia;
        }

        private double CalculateCreepCoefficient()
        {
            return 1.5;
        }

        private double CalculateSteelToConcreteCoefficient()
        {
            if (_rebars.Count() == 0)
                return 1;

            double creepCoefficient = CalculateCreepCoefficient();
            double concreteEffectiveModulus = _concreteSection.ConcreteClass.CalculateEffectiveElasticModulus(creepCoefficient);

            double steelModulus = _rebars.GetSteelClass().ElasticModulus;

            return steelModulus / concreteEffectiveModulus;
        }

        private double CalculateAreaOfConcreteEquivalentSection()
        {
            double area = _concreteSection.CalculateArea();
            area += _rebars.CalculateArea() * CalculateSteelToConcreteCoefficient();

            return area;
        }

        private double CalculateFirstMomentOfAreaFromTopEdge()
        {
            var firstMomentOfArea = _concreteSection.CalculateFirstMomentOfArea();

            double steelToConcreteCoefficient = CalculateSteelToConcreteCoefficient();
            foreach (Rebar rebar in _rebars)
            {
                double distanceFromTopEdge = rebar.CalculateDistanceFromTopEdge(_concreteSection.Height);
                firstMomentOfArea += rebar.CalculateArea() * steelToConcreteCoefficient * distanceFromTopEdge;
            }

            return firstMomentOfArea;
        }

        private double CalculateCenterOfGravityFromTopEdge()
        {
            return CalculateFirstMomentOfAreaFromTopEdge() / CalculateAreaOfConcreteEquivalentSection();
        }
    }

Ok, tu zaczyna się coś dziać, przeanalizujmy to od góry:

  • dwa pola: _concreteSection oraz _rebars inicjalizowane w konstruktorze,
  • publiczna metoda CalculateMomentOfInertia(), która dyryguje pomniejszymi metodami
  • zbiór metod prywatnych, czyli takich do których dostęp jest tylko w ramach klasy, które nie są dostępne z poziomu innych klas.

Na użytek tej części zdecydowałem się na uproszczenie – metoda CalculateCreepCoefficient() zwraca zawsze wartość 1.5. Nie chciałem dalej rozbudowywać programu uwzględniająć różne sytuacje, czasy i zależności od wymiaru charakterystycznego przekroju – przyjdzie na to czas.

Wydzielenie do nowego projektu pełnej funkcjonalności rodzi pytanie w jaki sposób wykorzystać ją w innych projektach. Programowanie bardzo często polega na sklejaniu różnych bibliotek w celu uzyskania nowego produktu. W naszym wypadku do dyspozycji mamy projekt/bibliotekę ConcreteLibrary. Wykorzystanie jej w początkowym projekcie PierwszaKonsola musimy zaczać od dodania odpowiedniej referencji. Prawym przyciskiem myszy kilknij na References w drzewie projektu do którego chcesz dodać nowe odniesienie.

W nowym oknie dialogowym ‘zaptaszkuj’ wzorcowy projekt, który chcesz wykorzystać:

Ostatnim krokiem do wykorzystania gotowej funkcjonalności jest wskazanie jakie przestrzenie nazw będa wykorzystywane w ramach danego pliku źródłowego. W pliku Program.cs, który jest punktem wejście w projekcie PierwszaKonsola musimy na samej górze dodać using ConcreteLibrary; , co podpowie kompilatorowi jakiej przestrzeni nazw będziemy używać. Kompletna wersja Program.cs przedstawiająca jak korzystać z biblioteki w innym projekcie znajduje się poniżej:

using System;
using ConcreteLibrary;

namespace PierwszaKonsola
{
    public class Program
    {
        public static void Main()
        {
            Concrete concreteClass = new Concrete("C30/37", 34 * Math.Pow(10, 9));
            Steel steelClass = new Steel("B500SP", 200 * Math.Pow(10, 9));

            double width = 0.25;
            double height = 0.50;
            ConcreteSection section = new ConcreteSection(width, height, concreteClass);

            double diameter = 0.016;
            double cover = 0.030;
            Rebar rebar = new Rebar(diameter, steelClass, cover + diameter / 2);

            RebarsInSection rebars = new RebarsInSection();
            rebars.AddRebar(rebar);

            ReinforcedSection reinforcedSection = new ReinforcedSection(section, rebars);

            string message1 = string.Format("Moment bezwładności wynosi {0:N8}", reinforcedSection.CalculateMomentOfInertia());
            Console.WriteLine(message1);
            Console.ReadLine();
        }
    }
}

Mam kilka zarzutów co do wygody stosowania tej biblioteki. Klasy betonu czy stali powinny miec możliwość automatycznego uzupełniania wartości poszczególnych parametrów – zajmiemy się tym w kolejnej części. Na dziś wystarczy nowości 🙂

Series Navigation

Leave a Reply

Your email address will not be published. Required fields are marked *