برنامج لتمثيل نظام لتوزيع المياه في ++C

برنامج لتمثيل نظام لتوزيع المياه

يمثل البرنامج التالي نظاما لتوزيع المياه , يحتوي علي تنكات ومواسير وصمامات ومهمات أخري . ويبين البرنامج مدي سهولة وضع فئات لمواقف خاصة . ويمكن بنفس المبادئ وضع نظم أخري للتوزيع مثل توزيع الكهرباء , أو التحكم في التدفق النقدي في النظم الإقتصادية .

ويبين الشكل نظاما تقليديا لتزيع المياه , والذي سوف يمثل عن طريق مجموعة البرامج التي سوف نعرض لها .

وسوف نعرض لملفين نفترض شراؤهما من منتج للبرمجيات , وهما pipe.h,pipe.cpp ثم لملف نفترض أنه ما سنكتبه بأنفسنا , وهو pipe_app.cpp . ويمكنك استخدام البرنامج EasyWin لصياغة النظام .

c  88

شكل نظام توزيع مياه نمطي

عناصر النظام

يتكون النظام من المهمات التالية :

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 في هذا البرنامج , وهذه مكمن الجمال في أسلوب الترميز , فهي تعمل مع البيانات الأولية والبيانات التي يضعها المبرمج علي حد سواء .



ولكن عليك أن تلاحظ أنه في حالة وجود مؤثر في إحدي دوال فئة التخزين لا يعمل مع نوع من أنواع البيانات , فإن الفئة لن يمكنها أن تخزنه .

التسميات: