برنامج لتمثيل نظام لتوزيع المياه في ++C
برنامج لتمثيل نظام لتوزيع المياه
يمثل البرنامج التالي نظاما لتوزيع المياه , يحتوي علي تنكات ومواسير وصمامات ومهمات أخري . ويبين البرنامج مدي سهولة وضع فئات لمواقف خاصة . ويمكن بنفس المبادئ وضع نظم أخري للتوزيع مثل توزيع الكهرباء , أو التحكم في التدفق النقدي في النظم الإقتصادية .
ويبين الشكل نظاما تقليديا لتزيع المياه , والذي سوف يمثل عن طريق مجموعة البرامج التي سوف نعرض لها .
وسوف نعرض لملفين نفترض شراؤهما من منتج للبرمجيات , وهما pipe.h,pipe.cpp ثم لملف نفترض أنه ما سنكتبه بأنفسنا , وهو pipe_app.cpp . ويمكنك استخدام البرنامج EasyWin لصياغة النظام .
شكل نظام توزيع مياه نمطي
عناصر النظام
يتكون النظام من المهمات التالية :
1- مصدر المياه source للنظام .
2- الأحمال , أو المستفيدون (يطلق عليهم المؤلف sink ) .
3- المواسير pipes . وتميز المواسير بمقاومتها لمرور المياه . ومن الطبيعي أن تكون كمية الماء الداخلية في ماسورة مساويا لكمية الماء الخارج منها .
4- التنك , وهو مكان التخزين للمياة , وفي نفس الوقت يعزل التدفق الداخل عن الخارج , بمعني أن التدفق الداخل إليه يمكن أن يكون مغايرا للتدفق الخارج منه .
5- سويتشات التحكم , يقوم سويتشات بمراقبة منسوب الماء في التنك , بحيث يضمنا ألا يقبض عن سعته , ولا أن ينقص عن حد معين .
6- الصمامات , وهي التي تتحكم في مرور المياه بأن تكون إما مفتوحة أو مغلقة . التدفق , الضغط والضغط المضاد .
كل عنصر من عناصر النظام يميز بثلاثة خواص ؛ التدفق , والضغط , والضغط المضاد . ونحن حين نوصل عناصر النظام معا , نراعي التناسق بين هذه الخصائص .
التدفق
الخصيصة ذات الاهتمام الأول عادة للعناصر هي التدفق flow , ويقصد به كمية المياه المارة في العنصر في زمن محدد , والتدفق الداخل والخارج متساويان لكل العناصر عدا التنكات .
الضغط
يعطي المصدر والتنكات مياه بضغط معين , ويسير التيار المائي نتيجة وجود ضغط بين نقطتين . و كلما كان الضغط كبيرا كان التدفق كبيرا . وحين يغلق صمام , فإن التدفق يتوقف , ويجب أن يكون الصمام من القوة بحيث يتحمل الضغط الناشئ والتنكات تعزل الضغط كما تفعل مع التدفق .
الضغط المضاد
يتكون الضغط بسبب مقاومة العناصر لسريان التيار , فمثلا إذا كانت الماسورة ضيقة , فإن التدفق سوف يكون صغيرا حتي ولو كان الضغط كبيرا . وسوف يسبب الضغط المضاد تقليلا للتدفق ليس فقط بالنسبة للعنصر المسبب له , ولكن لكل العناصر السابقة عليه .
دخول وخروج العناصر
قد تكون خصيصة ما متساوية عند مدخل ومخرج عنصر ما , فالتدفق متساو بالنسبة للمواسير عند الطرفين . ولكن الأمر ليس هكذا دائما , فمثلا , عندما يغلق صمام فإن الضغط عند مخرجه يكون صفرا , بينما يكون الضغط عند مدخله موجودا . كما أن التدفق عند مدخل ومخرج التنكات ليس متسويا .
وعلي ذلك , فإن كل عنصر سوف يميز بستة خصائص ؛ ثلاثة عند الدخول : الضغط (الأتي له من العنصر السابق عليه) , والضغط المضاد (الأتي من العنصر التالي له ) , والتدفق (الأتي من العنصر السابق ) , وثلاثة عند الخروج : الضغط (الذي يسببه علي العنصر التالي له) , والضغط المضاد (الذي يسببه علي العنصر السابق عليه) , والتدفق (الذي يرسله للعنصر التالي ) .
وبحسب خرج العنصر من بيانات دخله , وفي نفس الوقت من خصائصه الذاتية وحالة تشغيله , كمقاومة ماسورة , أو كون صمام مغلق أو مفتوح . وتحسب بيانات الخرج علي فترات منتظمة , تحت سيطرة توقيت Tick() .
إجراء التوصيلات
بالإضافة إلي توصيل الأجزاء بعضها بالبعض , فإنه يجب وضع تسلسل البيانات المتعلقة بالخصائص الأساسية للنظام , فيجري تسلسل التدفق من مصدر النظام هبوطا , والضغط بنفس الطريقة , ثم الضغط المضاد من أسفل النظام صعودا .
فروض للتبسيط
وضعنا الفروض التالية لتفادي تعقد الحسابات الرياضية :
- الضغط المضاد متناسب طرديا مع التدفق , بينما هو في الواقع سيكون في تناسب عكسي معه , ولكن ذلك سوف يعقد الحسابات بصورة كبيرة .
- الضغط والضغط المضاد لهما نفس وحدات التدفق , بحيث يحسب علي أنه الأصغر منهما , فمثلا إذا كان الضغط من المصدر 100 جالون في الدقيقة , والضغط المضاد من الماسورة 60 جالون في الدقيقة ,فإن التدفق يعتبر 60 جالون في الدقيقة . وليس هذا متفقا مع أساسيات علم الهيدروليكا , والتي تحتوي علي علاقات كعقدة لمثل هذه الحسابات .
- خرج التنكات ثابت , وهو افتراض معقول في حالة كون التنكات علي إرتفاع كبير بالنسبة للأحمال .
- بينما يكون النظام تماثليا analogue , أي دائم التغير , فإن نظامنا يجري بصورة رقمية digital , بمعني أن حالة النظام تؤخذ علي فترات معينة , بحيث لا يشعر النظام بما يحدث من تغيرات خلال هذه الفترات ] تسمي هذه العملية في نظم التحكم "المسح scanning"[ .
تصميم البرنامج
الهدف من هذا البرنامج هو وضع مجموعة من الفئات تيسر نمذجة نظام لتوزيع المياه , ومن السهل عليك معرفة أي عنصر تمثله فئة ما .
وإليك البرنامجين الموردين من شركة البرمجيات :
Pipe.h
Pipes.h
// pipes.h
// header file for pipes
#include <iostream> //for cout, etc.
#include <iomanip> //for setw
#include <conio.h> //for getch()
using namespace std;
const int infinity = 32767; //infinite back pressure
enum offon { off, on }; //status of valves and switches
class Tank; //for using Tank in Switch
////////////////////////////////////////////////////////////////
class Component //components (Pipe, Valve, etc.)
{
protected:
int inpress, outpress; //pressures in and out
int inbackp, outbackp; //back pressures in and out
int inflow, outflow; //flow in and out
public:
Component() : inpress(0), outpress(0), inbackp(0),
outbackp(0), inflow(0), outflow(0)
{ }
virtual ~Component() //virtual destructor
{ }
int Flow() const
{ return inflow; }
friend void operator >= (Component&, Component&);
friend void Tee(Component&, Component&, Component&);
};
////////////////////////////////////////////////////////////////
class Source : public Component //flow begins here
{
public:
Source(int outp)
{ outpress = inpress = outp; }
void Tick(); //update
};
////////////////////////////////////////////////////////////////
class Sink : public Component //flow ends here
{
public:
Sink(const int obp) //initialize backpressure
{ outbackp = inbackp = obp; }
void Tick(); //update
};
////////////////////////////////////////////////////////////////
class Pipe : public Component //connects other components,
{ //has resistance to flow
private:
int resist;
public:
Pipe(const int r) //initialize
{ inbackp = resist = r; }
void Tick(); //update
};
////////////////////////////////////////////////////////////////
class Valve : public Component //turns flow on or off
{
private:
offon status; //on (open) or off (closed)
public:
Valve(const offon s) //initialize status
{ status = s; }
offon& Status() //get and set status
{ return status; }
void Tick(); //update
};
////////////////////////////////////////////////////////////////
class Tank : public Component //stores water
{
private:
int contents; //water in tank (gals)
int maxoutpress; //max output pressure
public:
Tank(const int mop) //initialize to empty tank
{ maxoutpress = mop; contents = 0; }
int Contents() const //get contents
{ return(contents); }
void Tick();
};
////////////////////////////////////////////////////////////////
class Switch //activated by tank level
{ //can operate valves
private:
offon status; //'on' if contents > triggercap
int cap; //capacity where switch turns on
Tank* tankptr; //pointer to owner tank
public:
Switch(Tank *tptr, const int tcap) //initialize
{ tankptr = tptr; cap = tcap; status = off; }
int Status() const //get status
{ return(status); }
void Tick() //update status
{ status = (tankptr->Contents() > cap) ? on : off; }
};
////////////////////////////////////////////////////////////////
Pipe.cpp
Pipes.cpp
// pipes.cpp
// function definitions for pipes
#include "pipes.h" //class declarations
//--------------------------------------------------------------
//"flows into" operator: c1 >= c2
void operator >= (Component& c1, Component& c2)
{
c2.inpress = c1.outpress;
c1.inbackp = c2.outbackp;
c2.inflow = c1.outflow;
}
//--------------------------------------------------------------
//"tee" divides flow into two
void Tee(Component& src, Component& c1, Component& c2)
{
//avoid division by 0
if( (c1.outbackp==0 && c2.outbackp==0) ||
(c1.outbackp==0 && c2.outbackp==0) )
{
c1.inpress = c2.inpress = 0;
src.inbackp = 0;
c1.inflow = c2.inflow = 0;
return;
} //proportion for each output
float f1 = (float)c1.outbackp / (c1.outbackp + c2.outbackp);
float f2 = (float)c2.outbackp / (c1.outbackp + c2.outbackp);
//pressures for two outputs
c1.inpress = src.outpress * f1;
c2.inpress = src.outpress * f2;
//back pressure for single input
src.inbackp = c1.outbackp + c2.outbackp;
//flow for two outputs
c1.inflow = src.outflow * f1;
c2.inflow = src.outflow * f2;
}
//--------------------------------------------------------------
void Source::Tick() //update source
{ //output pressure fixed
outbackp = inbackp;
outflow = (outpress < outbackp) ? outpress : outbackp;
inflow = outflow;
}
//--------------------------------------------------------------
void Sink::Tick() //update sink
{ //output back pressure fixed
outpress = inpress;
outflow = (outbackp < outpress) ? outbackp : outpress;
inflow = outflow;
}
//--------------------------------------------------------------
void Pipe::Tick(void) //update pipes
{
outpress = (inpress < resist) ? inpress : resist;
outbackp = (inbackp < resist) ? inbackp : resist;
//outflow is the lesser of outpress, outbackp, and resist
if(outpress < outbackp && outpress < resist)
outflow = outpress;
else if(outbackp < outpress && outbackp < resist)
outflow = outbackp;
else
outflow = resist;
}
//--------------------------------------------------------------
void Valve::Tick(void) //update valves
{
if(status==on) //if valve open
{
outpress = inpress;
outbackp = inbackp;
outflow = (outpress < outbackp) ? outpress : outbackp;
}
else //if valve closed
{
outpress = 0;
outbackp = 0;
outflow = 0;
}
}
//--------------------------------------------------------------
void Tank::Tick(void) //update tanks
{
outbackp = infinity; //will take all the flow
// you can give it
if( contents > 0 ) //if not empty
{
outpress = (maxoutpress<inbackp) ? maxoutpress : inbackp;
outflow = outpress;
}
else //if empty
{
outpress = 0; //no out pressure,
outflow = 0; //no flow
}
contents += inflow - outflow; //always true
}
برمجة التوصيلات
الجزء الجوهري المتعلق بقابلية البرنامج للاستخدام هو وضع طريقة سهلة مبتكرة لوصف التوصيلات في البرنامج . بإمكاننا استخدام دالة من قبيل :
Connect( valvel, tank1) ;
علي أن ذلك يثير البلبلة , أي العنصرين سابق علي الأخر في مسار التدفق ؟
الإسلوب الأجدي هو أن نزيد تحميل مؤثر بحيث يمكن استغلاله للتعبير عن توصيل العناصر . ولقد وقع اختيارنا علي المؤثر >= لكي يعطي صورة مرئية لمسار التدفق , ويمكننا أن نسميه بعد زيادة تحميله " مؤثر التدفق إلي " بحيث تكون عبارة مثل :
Valve1 >= tank1;
معبرة عن أن التدفق يسري من الصمام إلي التانك المذكورين .
الفئات الأساسية والمشتقة
حين نصمم برنامجنا ننظر إلي الخصائص المتشابهة , ونجمع الأعم منها في فئات أساسية ونجعل الخصائص الذاتية مشتقة .
الفئة الأساسية component
نري في هذا البرنامج أن العناصر جميعها (عدا السوتشات) يسري بها تدفق الماء , ويمكن أن توصل ببعضها البعض . وعلي ذلك فسوف ننشئ فئة أساسية تسمح بهذا التصور , ونسميها component علي النحو التالي :
Class Component
{
Protected:
Int inpress, outpress; // pressure in and out
Int inback, outback; // backpressure in and out
Int inflow, outflow; //
Flow in and out
Public:
Component(void)
{
Outpress=inback=outback=inflow=outflow=0; }
Int flow(void)
{ return inflow; }
Friend void operator >= (Cpmponent&, Cpmponent&);
Friend void Tee(Component&, Cpmponent&)
};
لكل عنصر ضغط وضغط مضاد وتدفق , وكل منها قد تكون للدخل أو للخرج كل هذه البيانات مخزنة في الفئة الأساسية . وتقوم البادئة باستهلال هذه القيم بالصفر , كما توجد دالة تعيد التدفق , والذي هو في الغالب أهم بيان نريد حسابه لتأخذ فكرة عن وضع النظام .
المؤثر "تدفق إلي "
يقوم المؤثر "تدفق إلي " عنصرين متتالين في مسار التدفق . هنا يساوي بين خصائص الدخول الثلاثة للعنصر السابق بمثيلاتها للعنصر اللاحق , علي النحو التالي :
// ''flow into'' operator: c1 >=c2
Void operator >=(Component& c1, Cpmponent& c2)
{
C2.inpress = c1.outpress;
C1.inbackp = c2.inbackp;
C2.inflow = c1.outflow;
}
وقد عرفت دالة المؤثر علي أنها صديقة وكان من الممكن تعريفها كدالة منتمية , ولكن دالة أخري ؛ Tee() , يجب أن تعرف كدالة صديقة , ومن ثم فقد عرفت دالة المؤثر كدالة صديقة لأجل التوافق وقد جعل الإمرار في دالة المؤثر مرجعيا , حيث إن المعاملين ذاتها يجب تغييرهما معا .
وحيث إن المؤثر >= يطبق علي كائنات الفئة الأساسية , فهو يطبق بالتبعية علي كائنات الفئات المشتقة كالتنكات والصمامات , ويوفر عليك ذلك جهد تعريف دالة لتتعامل مع كل نوع من التوصيلات , فمثلا :
Friend void operator >= (Pipe&, Valve&);
Friend void operator >= (Valve&, Tank&);
Friend void operator >=(Valve&, Sink&);
وهكذا .
الفئات المشتقة
لدينا الفئات المشتقة التالية Source, Sink, Pipe, Valve, Tank , ولكل فئة خصائصها الذاتية . فالفئة Source ذات ضغط ثابت , بينما Sink ذات ضغط مضاد ثابت والفئة Pipe ذات مقاومة داخلية ثابتة , ومن ثم فلا يمكن للضغط المضاد لها أن يكون أكبر مقدار معين . وتتميز الفئة Valve بوضعين يعرفان كبيانات معددة enum من النوع :offon وهما on, off للمتغير status قيمة تعبر عن حجم الماء المختزن . وتتغير حالة الصمامات وسعة التنكات مع سريان البرنامج .
وبالنسبة للبيانات الثابتة , مثل مقاومة المواسير أو ضغط المصدر , فهي تستهل مع إنشاء الكائن .
ونلاحظ أن كل هذه العناصر , وكذا العنصر switch تحتوي علي دالة Tick() , تستدعي علي فترات ثابتة لتحديث وضع كل عنصر , واحدا بعد الأخر.
الدالة Tee()
تقوم هذه الدالة بتمثيل وصلة "تي" , والتي تفرع مسارا داخلا إلي مسارين خارجين . ويقسم التدفق الداخل بين التدفقين الخارجيين كتناسب عكسي للضغطين المضادين , فالماسورة التي تعطي ضغطا مضادا أكبر تحصل علي قدر أقل من التدفق .
وتستدعي هذه الدالة بثلاثة عوامل , بالترتيب التالي :
Tee(input1, output1, output2);
وللأسف ليس بإمكاننا أن نزيد تحميل مؤثر ليؤدي هذه المهمة كما فعلنا مع التوصيل المعتاد , لتكون الصياغة أكثر رشاقة , وذلك لأنه لا يوجد مؤثر ثلاثي التأثير ] عدا المؤثر الشرطي [ "؟" [ .
الفئة Switch
الفئة Switch لها علاقة خاصة بالفئة Tank فكل تانك له سويتشان , يعمل أحدهما حين تنخفض سعة التانك عن مستوي معين , والأخر حين تزيد عن حد محين .
فلنعبر عن هذه العلاقة الخاصة بين التانك وسوتشيه بقولنا أن التانك " يحوز own " السوتشين . فحين ينشأ سويتشين , يلحق به قيمتان أحدهما عنوان التانك الذي يحوزه ,
Templist2.cpp
وقد استخمنا في البرنامج متغيرا احتياطيا emptemp لكي يخزن فيه البيان قبل إضافته للقائمة . وتلاحظ أننا لم نحتج لأي تعديل في صياغة الفئة LinkList في هذا البرنامج , وهذه مكمن الجمال في أسلوب الترميز , فهي تعمل مع البيانات الأولية والبيانات التي يضعها المبرمج علي حد سواء .
ولكن عليك أن تلاحظ أنه في حالة وجود مؤثر في إحدي دوال فئة التخزين لا يعمل مع نوع من أنواع البيانات , فإن الفئة لن يمكنها أن تخزنه .
تعليقات
إرسال تعليق