]> Devoid-pointer.net GitWeb - Nine-Q.git/commitdiff
First implementation of "Titration Curve" suite plus necessary infrastructure improve...
authorMichal Malý <madcatxster@devoid-pointer.net>
Mon, 15 Dec 2014 22:29:14 +0000 (23:29 +0100)
committerMichal Malý <madcatxster@devoid-pointer.net>
Mon, 15 Dec 2014 22:29:14 +0000 (23:29 +0100)
16 files changed:
bin/resources/noimage.png [new file with mode: 0644]
bin/templates/face_index.html
bin/templates/face_titration_curve.html [new file with mode: 0644]
bin/templates/titration_curve_answer_section.html [new file with mode: 0644]
nine_q.gpr
src/face_generators/face_generator.adb
src/face_generators/face_generator.ads
src/handlers/handler_resources.ads [new file with mode: 0644]
src/handlers/handlers.adb
src/problem_generators/problem_generator-acidobazic_suite.adb
src/problem_generators/problem_generator-solubility_suite.adb
src/problem_generators/problem_generator-titration_curve_suite.adb
src/problem_generators/problem_generator.ads
src/problem_generators/problem_generator_syswides.ads
src/problem_manager.adb
src/problem_manager.ads

diff --git a/bin/resources/noimage.png b/bin/resources/noimage.png
new file mode 100644 (file)
index 0000000..cae466e
Binary files /dev/null and b/bin/resources/noimage.png differ
index f1320be9c996c424e41479646298ffd77e8abca2..47ef538c75f8060fb1898070305ca1c34a2940d7 100644 (file)
@@ -1,4 +1,5 @@
-  <a class="main_navlink" href="/start?problem_category=ACIDOBAZIC">pH jednosytné kyseliny/báze</a>
-  <a class="main_navlink" href="/start?problem_category=SOLUBILITY">Srážecí rovnováhy</a>
+  <a class="main_navlink" href="/start?problem_category=ACIDOBAZIC">pH jednosytné kyseliny/báze</a><br />
+  <a class="main_navlink" href="/start?problem_category=SOLUBILITY">Srážecí rovnováhy</a><br />
+  <a class="main_navlink" href="/start?problem_category=TITRATION_CURVE">Titrační křivka</a><br />
 
 
diff --git a/bin/templates/face_titration_curve.html b/bin/templates/face_titration_curve.html
new file mode 100644 (file)
index 0000000..348ec5b
--- /dev/null
@@ -0,0 +1,89 @@
+
+  <div class="backgrounded_block">
+    <div class="caption_v1">Zadání:</div>
+    <div class="assignment_text"><span class="key_info">@_SAMPLE_VOLUME_INT_@,@_SAMPLE_VOLUME_DEC_@&nbsp;.&nbsp;10<span class="exponent">@_SAMPLE_VOLUME_EXP_@</span>&nbsp;dm<span class="exponent">3</span> dvojsytné @_SAMPLE_TYPE_@</span> o koncentraci <span class="key_info">@_SAMPLE_CONC_INT_@,@_SAMPLE_CONC_DEC_@&nbsp;.&nbsp;10<span class="exponent">@_SAMPLE_CONC_EXP_@</span>&nbsp;mol/L</span> o <span class="key_info">pKx1 = @_PKX1_INT_@,@_PKX1_DEC_@</span> a <span class="key_info">pKx2&nbsp;=&nbsp;@_PKX2_INT_@,@_PKX2_DEC_@</span> je titrováno odměrným roztokem jednosytné <span class="key_info">@_TITRANT_TYPE_@</span> o koncentraci <span class="key_info">@_TITRANT_CONC_INT_@,@_TITRANT_CONC_DEC_@&nbsp;.&nbsp;10<span class="exponent">@_TITRANT_CONC_EXP_@</span></span>&nbsp;mol/L. Spočítejte pH roztoku a objem přidaného titračního činidla v těchto bodech:
+      <div>
+        <ul>
+          <li>Na počátku titrace</li>
+          <li>V polovině první ekvivalence</li>
+          <li>V první ekvivalenci</li>
+          <li>V polovině druhé ekvivalence</li>
+          <li>V druhé ekvivalenci</li>
+          <li>V 1,5násobku druhé ekvivalence</li>
+        </ul>
+      </div>
+    <form class="problem_form" method="post" action="/check_answer" enctype="multipart/form-data">
+       <input type="hidden" name="@_RESERVED__PROBLEM_ID_@" value="@_RESERVED__PROBLEM_ID_VAL_@">
+        <div class="form_line">
+          Počátek:
+         <label class="form_label_ac" for="@_ANSWER_PH_START_@">pH:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_PH_START_@" maxlength="128" value="" />
+       </div>
+
+        <div class="form_line">
+         Polovina první ekvivalence:
+         <label class="form_label_ac" for="@_ANSWER_PH_FIRST_HALF_@">pH:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_PH_FIRST_HALF_@" maxlength="128" value="" />
+          <label class="form_label_ac" for="@_ANSWER_VOLUME_FIRST_HALF_@">Objem [mL]:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_VOLUME_FIRST_HALF_@" maxlength="128" value="" />
+       </div>
+
+        <div class="form_line">
+         První ekvivalence:
+         <label class="form_label_ac" for="@_ANSWER_PH_FIRST_EQUIV_@">pH:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_PH_FIRST_EQUIV_@" maxlength="128" value="" />
+          <label class="form_label_ac" for="@_ANSWER_VOLUME_FIRST_EQUIV_@">Objem [mL]:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_VOLUME_FIRST_EQUIV_@" maxlength="128" value="" />
+       </div>
+
+        <div class="form_line">
+         Polovina druhé ekvivalence:
+         <label class="form_label_ac" for="@_ANSWER_PH_SECOND_HALF_@">pH:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_PH_SECOND_HALF_@" maxlength="128" value="" />
+          <label class="form_label_ac" for="@_ANSWER_VOLUME_SECOND_HALF_@">Objem [mL]:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_VOLUME_SECOND_HALF_@" maxlength="128" value="" />
+       </div>
+
+        <div class="form_line">
+         Druhá ekvivalence:
+         <label class="form_label_ac" for="@_ANSWER_PH_SECOND_EQUIV_@">pH:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_PH_SECOND_EQUIV_@" maxlength="128" value="" />
+          <label class="form_label_ac" for="@_ANSWER_VOLUME_SECOND_EQUIV_@">Objem [mL]:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_VOLUME_SECOND_EQUIV_@" maxlength="128" value="" />
+       </div>
+
+        <div class="form_line">
+         Nadbytek:
+         <label class="form_label_ac" for="@_ANSWER_PH_OVER_SECOND_EQUIV_@">pH:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_PH_OVER_SECOND_EQUIV_@" maxlength="128" value="" />
+          <label class="form_label_ac" for="@_ANSWER_VOLUME_OVER_SECOND_EQUIV_@">Objem [dm<span class="exponent">3</span>]:</label>
+         <input class="form_input_ac" type="text" name="@_ANSWER_VOLUME_OVER_SECOND_EQUIV_@" maxlength="128" value="" />
+       </div>
+
+       <div class="form_line">
+         <input type="submit" name="send_answer" value="Zkontrolovat" />
+        </div>
+    </form>
+    </div>
+  </div>
+
+    @_ANSWER_SECTION_@
+
+    <div class="backgrounded_block">
+      <span class="expand_section" onclick="show_hide_item('hints_section', 'hints_button', 'block', '/images/arrow_right.png', '/images/arrow_down.png')">
+       <img class="expand_section" id="hints_button" src="/images/arrow_right.png" alt="Zobraz sekci" title="Zobraz sekci" />
+      </span>
+      <div class="caption_v1">Nápověda:</div>
+      <div class="assignment_hint" id="hints_section">
+       @_HINTS_SECTION_@
+      </div>
+    </div>
+
+    <div class="backgrounded_block">
+      <form class="problem_form" method="post" action="/next_problem" enctype="multipart/form-data">
+       <input type="hidden" name="@_RESERVED__PROBLEM_CATEGORY_@" value="@_RESERVED__PROBLEM_CATEGORY_VAL_@">
+       <div class="form_line">
+         <input type="submit" name="next_problem" value="Další příklad" />
+       </div>
+      </form>
+    </div>
diff --git a/bin/templates/titration_curve_answer_section.html b/bin/templates/titration_curve_answer_section.html
new file mode 100644 (file)
index 0000000..bfde9f7
--- /dev/null
@@ -0,0 +1,4 @@
+  <div class="backgrounded_block">
+    <div class="@_ANSWER_KIND_@">@_ANSWER_MESSAGE_@</div>
+    <img class="titration_curve_image" src="@_TITRATION_CURVE_IMAGE_PATH_@" />
+  </div>
index ece67dca7f7d403d5b4078270963da43600075f4..490689663df0cf14e932eaa60fe16b821eff8db8 100644 (file)
@@ -1,4 +1,5 @@
 with "/opt/gnat/lib/gnat/aws.gpr";
+with "/opt/gnat/lib/gnat/gtkada.gpr";
 --with "aws.gpr";
 
 project Nine_Q is
index 41f47c8624805a6dc11fe8d9f8406949a0c28212..cac0ea3170e9a00bbe1eee94fff3cacd9111db90 100644 (file)
@@ -70,6 +70,8 @@ package body Face_Generator is
         return Generate_Face_Acidobazic(Assignment, Answer_Message, Answer_Code, Parameters, HTML, Pr_ID, Pr_Cat);
       elsif Problem_Type_Str = PROBLEM_TYPE_SOLUBILITY then
         return Generate_Face_Solubility(Assignment, Answer_Message, Answer_Code, Parameters, HTML, Pr_ID, Pr_Cat);
+      elsif Problem_Type_Str = PROBLEM_TYPE_TITRATION_CURVE then
+       return Generate_Face_Titration_Curve(Assignment, Answer_Message, Answer_Code, Parameters, HTML, Pr_ID, Pr_Cat);
       else
        return E_INVAL;
       end if;
@@ -368,5 +370,91 @@ package body Face_Generator is
 
     return OK;
   end Generate_Face_Solubility;
+  
+  function Generate_Face_Titration_Curve(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map;
+                                        Answer_Message: in UB_Text;
+                                        Answer_Code: in Problem_Generator_Syswides.Answer_RetCode;
+                                        Parameters: in Problem_Generator_Syswides.Parameters_Info.Map;
+                                        HTML: out HTML_Code; Pr_ID: in String; Pr_Cat: in String) return RetCode is
+    use AWS.Templates;
+    use Problem_Generator_Syswides;
+    use Problem_Generator_Syswides.Assignment_Info;
+    use Problem_Generator_Syswides.Parameters_Info;
+
+    Translations_Hdr: Translate_Set;
+    Translations: Translate_Set;
+    Translations_Answer: Translate_Set;
+    Temp: HTML_Code;
+  begin
+    Insert(Translations_Hdr, Assoc(HEADER_CAPTION_KEY, "&lt;&nbsp;" & Titration_Curve_Suite.PROBLEM_NAME_READABLE));
+    Insert(Translations_Hdr, Assoc(META_EXPIRE_NOW_KEY, META_EXPIRE_NOW_TEXT));
+    Insert(Translations_Hdr, Assoc(META_NO_CACHE_KEY, META_NO_CACHE_TEXT));
+    HTML := Parse(Filename => "templates/header.html", Translations => Translations_Hdr);
+    -- Add JavaScripts
+    Temp := Parse(Filename => "scripts/expand_collapse.js", Cached => True);
+    Append_HTML(Source => HTML, New_Item => Temp);
+
+    -- Mandatory hidden parameters
+    Insert(Translations, Assoc(RESERVED_PROBLEM_ID_KEY, RESERVED_PROBLEM_ID_KEY));
+    Insert(Translations, Assoc(RESERVED_PROBLEM_ID_VAL_KEY, Pr_ID));
+    Insert(Translations, Assoc(RESERVED_PROBLEM_CATEGORY_KEY, RESERVED_PROBLEM_CATEGORY_KEY));
+    Insert(Translations, Assoc(RESERVED_PROBLEM_CATEGORY_VAL_KEY, Pr_Cat));
+    -- Assignment
+    Insert(Translations, Assoc(Titration_Curve_Suite.SAMPLE_CONC_INT_KEY, Assignment.Element(Titration_Curve_Suite.SAMPLE_CONC_INT_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.SAMPLE_CONC_DEC_KEY, Assignment.Element(Titration_Curve_Suite.SAMPLE_CONC_DEC_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.SAMPLE_CONC_EXP_KEY, Assignment.Element(Titration_Curve_Suite.SAMPLE_CONC_EXP_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.SAMPLE_TYPE_KEY, Assignment.Element(Titration_Curve_Suite.SAMPLE_TYPE_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.TITRANT_TYPE_KEY, Assignment.Element(Titration_Curve_Suite.TITRANT_TYPE_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.SAMPLE_VOLUME_INT_KEY, Assignment.Element(Titration_Curve_Suite.SAMPLE_VOLUME_INT_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.SAMPLE_VOLUME_DEC_KEY, Assignment.Element(Titration_Curve_Suite.SAMPLE_VOLUME_DEC_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.SAMPLE_VOLUME_EXP_KEY, Assignment.Element(Titration_Curve_Suite.SAMPLE_VOLUME_EXP_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.TITRANT_CONC_INT_KEY, Assignment.Element(Titration_Curve_Suite.TITRANT_CONC_INT_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.TITRANT_CONC_DEC_KEY, Assignment.Element(Titration_Curve_Suite.TITRANT_CONC_DEC_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.TITRANT_CONC_EXP_KEY, Assignment.Element(Titration_Curve_Suite.TITRANT_CONC_EXP_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.PKX1_INT_KEY, Assignment.Element(Titration_Curve_Suite.PKX1_INT_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.PKX1_DEC_KEY, Assignment.Element(Titration_Curve_Suite.PKX1_DEC_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.PKX2_INT_KEY, Assignment.Element(Titration_Curve_Suite.PKX2_INT_KEY)));
+    Insert(Translations, Assoc(Titration_Curve_Suite.PKX2_DEC_KEY, Assignment.Element(Titration_Curve_Suite.PKX2_DEC_KEY)));
+    --
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_PH_START_KEY, Titration_Curve_Suite.ANSWER_PH_START_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_VOLUME_START_KEY, Titration_Curve_Suite.ANSWER_VOLUME_FIRST_HALF_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_PH_FIRST_HALF_KEY, Titration_Curve_Suite.ANSWER_PH_FIRST_HALF_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_VOLUME_FIRST_HALF_KEY, Titration_Curve_Suite.ANSWER_VOLUME_FIRST_HALF_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_PH_FIRST_EQUIV_KEY, Titration_Curve_Suite.ANSWER_PH_FIRST_EQUIV_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_VOLUME_FIRST_EQUIV_KEY, Titration_Curve_Suite.ANSWER_VOLUME_FIRST_EQUIV_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_PH_SECOND_HALF_KEY, Titration_Curve_Suite.ANSWER_PH_SECOND_HALF_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_VOLUME_SECOND_HALF_KEY, Titration_Curve_Suite.ANSWER_VOLUME_SECOND_HALF_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_PH_SECOND_EQUIV_KEY, Titration_Curve_Suite.ANSWER_PH_SECOND_EQUIV_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_VOLUME_SECOND_EQUIV_KEY, Titration_Curve_Suite.ANSWER_VOLUME_SECOND_EQUIV_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_PH_OVER_SECOND_EQUIV_KEY, Titration_Curve_Suite.ANSWER_PH_OVER_SECOND_EQUIV_KEY));
+    Insert(Translations, Assoc(Titration_Curve_Suite.ANSWER_VOLUME_OVER_SECOND_EQUIV_KEY, Titration_Curve_Suite.ANSWER_VOLUME_OVER_SECOND_EQUIV_KEY));
+
+    case Answer_Code is
+      when Correct_Answer =>
+       Insert(Translations_Answer, Assoc(ANSWER_KIND_KEY, ANSWER_KIND_GOOD));
+       Insert(Translations_Answer, Assoc(ANSWER_MESSAGE_KEY, UB_Text_To_Fixed_String(Answer_Message)));
+       Insert(Translations_Answer, Assoc(Titration_Curve_Suite.TITRATION_CURVE_IMAGE_PATH_KEY, Assignment.Element(Titration_Curve_Suite.TITRATION_CURVE_IMAGE_PATH_KEY)));
+       Temp := Parse(Filename => "templates/titration_curve_answer_section.html", Translations => Translations_Answer);
+       Insert(Translations, Assoc(ANSWER_SECTION_KEY, HTML_To_Fixed_String(Temp)));
+      when Wrong_Answer | Malformed_Answer =>
+       Insert(Translations_Answer, Assoc(ANSWER_KIND_KEY, ANSWER_KIND_BAD));
+       Insert(Translations_Answer, Assoc(ANSWER_MESSAGE_KEY, UB_Text_To_Fixed_String(Answer_Message)));
+       Insert(Translations_Answer, Assoc(Titration_Curve_Suite.TITRATION_CURVE_IMAGE_PATH_KEY, "/resources/noimage.png"));
+       Temp := Parse(Filename => "templates/titration_curve_answer_section.html", Translations => Translations_Answer);
+       Insert(Translations, Assoc(ANSWER_SECTION_KEY, HTML_To_Fixed_String(Temp)));
+      when others =>
+       Insert(Translations, Assoc(ANSWER_SECTION_KEY, ""));
+    end case;
+
+    Temp := Parse(Filename => "templates/face_titration_curve.html", Translations => Translations);
+
+    Append_HTML(Source => HTML, New_Item => Temp);
+
+    Temp := Parse(Filename => "templates/footer.html");
+    Append_HTML(Source => HTML, New_Item => Temp);
+
+    return OK;
+  end Generate_Face_Titration_Curve;
+
 
 end Face_Generator;
index 5e546d06a543d3a7d012e6804fdcb270250fce90..ba5138852d9f5bd28f7fa9617b2045f57bdee64a 100644 (file)
@@ -36,6 +36,13 @@ private
                                    HTML: out HTML_Code;
                                    Pr_ID: in String; Pr_Cat: in String) return RetCode;
 
+  function Generate_Face_Titration_Curve(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map;
+                                        Answer_Message: in UB_Text;
+                                        Answer_Code: in Problem_Generator_Syswides.Answer_RetCode;
+                                        Parameters: in Problem_Generator_Syswides.Parameters_Info.Map;
+                                        HTML: out HTML_Code;
+                                        Pr_ID: in String; Pr_Cat: in String) return RetCode;
+
   ERROR_MESSAGE_KEY: constant String := "ERROR_MESSAGE";
   HEADER_CAPTION_KEY: constant String := "HEADER_CAPTION";
   HINTS_SECTION_KEY: constant String := "HINTS_SECTION";
diff --git a/src/handlers/handler_resources.ads b/src/handlers/handler_resources.ads
new file mode 100644 (file)
index 0000000..1a45247
--- /dev/null
@@ -0,0 +1,5 @@
+with AWS.Dispatchers.Callback;
+
+package Handler_Resources is
+  function Callback return AWS.Dispatchers.Callback.Handler;
+end Handler_Resources;
index 3bf75c0ad69aba16600063902af4a8095212d5da..2af5b58d4abc24b119f73068ac302f4424e084a4 100644 (file)
@@ -2,6 +2,7 @@ with Handler_Check_Answer;
 with Handler_Default;
 with Handler_Images;
 with Handler_Next_Problem;
+with Handler_Resources;
 with Handler_Start;
 with Handler_Styles;
 
@@ -21,6 +22,9 @@ package body Handlers is
                     Action => Handler_Start.Callback);
     Handler.Register(URI => "/main_stylesheet",
                     Action => Handler_Styles.Main_Callback);
+    Handler.Register(URI => "/resources",
+                    Action => Handler_Resources.Callback,
+                    Prefix => True);
 
     return Handler;
   end Get_Dispatchers;
index cde1cd61bc610994efa5359de6c91bf802e011f8..12ef83ac9387475c16d1024a39e85438b9016da1 100644 (file)
@@ -87,7 +87,7 @@ package body Acidobazic_Suite is
     end if;
   end Check_Answer;
 
-  function Get_Assignment(Problem: in out Acidobazic_Problem; Assignment: in out Assignment_Info.Map) return RetCode is
+  function Get_Assignment(Problem: in out Acidobazic_Problem; Assignment: in out Assignment_Info.Map; Resource_Prefix: in String) return RetCode is
     Guard: Auto_Lock.LC;
     C: Assignment_Info.Cursor;
     Success: Boolean;
index d2876da01c5cd8664b678fd146f20d15b929331c..fad528725fc27d2eff23f5a845449e93731fcbd4 100644 (file)
@@ -69,7 +69,7 @@ package body Solubility_Suite is
     end if;
   end Check_Answer;
 
-  function Get_Assignment(Problem: in out Solubility_Problem; Assignment: in out Assignment_Info.Map) return RetCode is
+  function Get_Assignment(Problem: in out Solubility_Problem; Assignment: in out Assignment_Info.Map; Resource_Prefix: in String) return RetCode is
     package FH is new Formatting_Helpers(SS_Float);
     use FH;
 
index c54fec3444466474d64ca519a2876f364f891c4f..835a019db97786ad6c78c8da7ccb16132ea440b3 100644 (file)
+with Ada.Numerics.Discrete_Random;
+with Ada.Numerics.Float_Random;
+with Ada.Numerics.Generic_Elementary_Functions;
+with Ada.Directories;
+with Ada.Strings.Fixed;
+with Ada.Strings.Unbounded;
+with Formatting_Helpers;
+with Cairo.Png;
+with Cairo.Image_Surface;
+with Glib;
+with Interfaces.C;
+with Interfaces.C.Strings;
+
+with Ada.Text_IO;
+
 separate(Problem_Generator)
 
 package body Titration_Curve_Suite is
 
   -- BEGIN: Inherited functions
   function Create return access Titration_Curve_Problem is
+    Problem: access Titration_Curve_Problem;
   begin
-    return new Titration_Curve_Problem;
+    Problem := new Titration_Curve_Problem;
+    return Problem;
   end Create;
 
   function Check_Answer(Problem: in out Titration_Curve_Problem; Answer: in Answer_Info.Map;
                         Message: out UB_Text) return Answer_RetCode is
+    package FH is new Formatting_Helpers(T_Float);
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    package TFIO is new Ada.Text_IO.Float_IO(T_Float);
+    use FH;
+    use TFEF;
+    use TFIO;
+
+    Kx1: T_Float := 10.0 ** (-Problem.pKx1);
+    Kx2: T_Float := 10.0 ** (-Problem.pKx2);
+    pH_Start: T_Float;
+    pH_Half_First: T_Float;
+    pH_First_Equiv: T_Float;
+    pH_Half_Second: T_Float;
+    pH_Second_Equiv: T_Float;
+    pH_Over_Second_Equiv: T_Float;
+
+    V_Half_First: T_Float;
+    V_First_Equiv: T_Float;
+    V_Half_Second: T_Float;
+    V_Second_Equiv: T_Float;
+    V_Over_Second_Equiv: T_Float;
+
+    First_Guess: T_Float;
+
+    pH_Start_Ans: T_Float;
+    pH_First_Half_Ans: T_Float;
+    pH_First_Equiv_Ans: T_Float;
+    pH_Second_Half_Ans: T_Float;
+    pH_Second_Equiv_Ans: T_Float;
+    pH_Over_Second_Equiv_Ans: T_Float;
+
+    V_First_Half_Ans: T_Float;
+    V_First_Equiv_Ans: T_Float;
+    V_Second_Half_Ans: T_Float;
+    V_Second_Equiv_Ans: T_Float;
+    V_Over_Second_Equiv_Ans: T_Float;
+
+    Surface: Cairo.Cairo_Surface;
+    Ctx: Cairo.Cairo_Context;
+    Status_Out: Cairo.Cairo_Status;
   begin
-    return No_Answer;
+    -- Init Cairo
+    Surface := Cairo.Image_Surface.Create(Cairo.Image_Surface.CAIRO_FORMAT_ARGB32, IMAGE_WIDTH, IMAGE_HEIGHT);
+    Ctx := Cairo.Create(Surface);
+
+    pH_Start := -Log(Base => 10.0, X => (Kx1 * Problem.Sample_Concentration) ** 0.5);
+    pH_Half_First := -Log(Base => 10.0, X => Kx1);
+    pH_First_Equiv := 0.5 * (-Log(Base => 10.0, X => Kx1) - Log(Base => 10.0, X => Kx2));
+    pH_Half_Second := -Log(Base => 10.0, X => Kx2);
+
+    V_Half_First := (Problem.Sample_Volume * Problem.Sample_Concentration / Problem.T_Concentration) * 0.5;
+    V_First_Equiv := V_Half_First * 2.0;
+    V_Half_Second := V_Half_First + V_First_Equiv;
+    V_Second_Equiv := V_First_Equiv * 2.0;
+    V_Over_Second_Equiv := V_Second_Equiv * 1.5;
+
+    case Problem.SType is
+      when ACID =>
+       pH_Second_Equiv := -Log(Base => 10.0, X => (((Kx2) * KW) / (Problem.Sample_Concentration * Problem.Sample_Volume / V_Second_Equiv)) ** 0.5);
+       pH_Over_Second_Equiv := 14.0 + Log(Base => 10.0, X => (Problem.T_Concentration * V_Over_Second_Equiv - (2.0 * Problem.Sample_Concentration * Problem.Sample_Volume)) / (V_Over_Second_Equiv + Problem.Sample_Volume));
+      when BASE =>
+       pH_Start := 14.0 - pH_Start;
+       pH_Half_First := 14.0 - pH_Half_First;
+       pH_First_Equiv := 14.0 - pH_First_Equiv;
+       pH_Half_Second := 14.0 - pH_Half_Second;
+       pH_Second_Equiv := -Log(Base => 10.0, X => ((KW * (Problem.Sample_Concentration * Problem.Sample_Volume / V_Second_Equiv)) / Kx2) ** 0.5);
+       pH_Over_Second_Equiv := -Log(Base => 10.0, X => (Problem.T_Concentration * V_Over_Second_Equiv - (2.0 * Problem.Sample_Concentration * Problem.Sample_Volume)) / (V_Over_Second_Equiv + Problem.Sample_Volume));
+    end case;
+
+    --Ada.Text_IO.Put_Line("Sample type: " & Sample_Type'Image(Problem.SType));
+    --Ada.Text_IO.Put("Sample concentration: "); TFIO.Put(Problem.Sample_Concentration); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("Sample Volume: "); TFIO.Put(Problem.Sample_Volume); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("Titrimetric solution: "); TFIO.Put(Problem.T_Concentration); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("pKx1: "); TFIO.Put(-Log(Base => 10.0, X => Kx1)); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("pKx2: "); TFIO.Put(-Log(Base => 10.0, X => Kx2)); Ada.Text_IO.New_Line;
+
+    --Ada.Text_IO.Put("pH_Start: "); TFIO.Put(pH_Start); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("V_1_H: "); TFIO.Put(V_Half_First); Ada.Text_IO.Put(" pH_1_H: "); TFIO.Put(pH_Half_First); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("V_1_Eq: "); TFIO.Put(V_First_Equiv); Ada.Text_IO.Put(" V_1_Eq: "); TFIO.Put(pH_First_Equiv); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("V_2_H: "); TFIO.Put(V_Half_Second); Ada.Text_IO.Put(" pH_2_H: "); TFIO.Put(pH_Half_Second); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("V_2_Eq: "); TFIO.Put(V_Second_Equiv); Ada.Text_IO.Put(" pH_2_Eq: "); TFIO.Put(pH_Second_Equiv); Ada.Text_IO.New_Line;
+    --Ada.Text_IO.Put("V_2_1.5x: "); TFIO.Put(V_Over_Second_Equiv); Ada.Text_IO.Put(" pH_2_1.5x: "); TFIO.Put(pH_Over_Second_Equiv); Ada.Text_IO.New_Line;
+
+    case Problem.SType is
+      when ACID =>
+       First_Guess := 10.0 ** (-pH_Start);
+      when BASE =>
+       First_Guess := 10.0 ** (-pH_Over_Second_Equiv);
+    end case;
+
+    declare
+      pH_Start_Ans_S: constant String := Answer.Element(ANSWER_PH_START_KEY);
+      pH_First_Half_Ans_S: constant String := Answer.Element(ANSWER_PH_FIRST_HALF_KEY);
+      pH_First_Equiv_Ans_S: constant String := Answer.Element(ANSWER_PH_FIRST_EQUIV_KEY);
+      pH_Second_Half_Ans_S: constant String := Answer.Element(ANSWER_PH_SECOND_HALF_KEY);
+      pH_Second_Equiv_Ans_S: constant String := Answer.Element(ANSWER_PH_SECOND_EQUIV_KEY);
+      pH_Over_Second_Equiv_Ans_S: constant String := Answer.Element(ANSWER_PH_OVER_SECOND_EQUIV_KEY);
+      --
+      V_First_Half_Ans_S: constant String := Answer.Element(ANSWER_VOLUME_FIRST_HALF_KEY);
+      V_First_Equiv_Ans_S: constant String := Answer.Element(ANSWER_VOLUME_FIRST_EQUIV_KEY);
+      V_Second_Half_Ans_S: constant String := Answer.Element(ANSWER_VOLUME_SECOND_HALF_KEY);
+      V_Second_Equiv_Ans_S: constant String := Answer.Element(ANSWER_VOLUME_SECOND_EQUIV_KEY);
+      V_Over_Second_Equiv_Ans_S: constant String := Answer.Element(ANSWER_VOLUME_OVER_SECOND_EQUIV_KEY);
+    begin
+      pH_Start_Ans := FH.String_To_Float(pH_Start_Ans_S);
+      pH_First_Half_Ans := FH.String_To_Float(pH_First_Half_Ans_S);
+      pH_First_Equiv_Ans := FH.String_To_Float(pH_First_Equiv_Ans_S);
+      pH_Second_Half_Ans := FH.String_To_Float(pH_Second_Half_Ans_S);
+      pH_Second_Equiv_Ans := FH.String_To_Float(pH_Second_Equiv_Ans_S);
+      pH_Over_Second_Equiv_Ans := FH.String_To_Float(pH_Over_Second_Equiv_Ans_S);
+
+      V_First_Half_Ans := FH.String_To_Float(V_First_Half_Ans_S) / 1000.0;
+      V_First_Equiv_Ans := FH.String_To_Float(V_First_Equiv_Ans_S) / 1000.0;
+      V_Second_Half_Ans := FH.String_To_Float(V_Second_Half_Ans_S) / 1000.0;
+      V_Second_Equiv_Ans := FH.String_To_Float(V_Second_Equiv_Ans_S) / 1000.0;
+      V_Over_Second_Equiv_Ans := FH.String_To_Float(V_Over_Second_Equiv_Ans_S) / 1000.0;
+    exception
+      when Constraint_Error =>
+       Message := To_UB_Text("Nesprávně zadané údaje");
+       return Malformed_Answer;
+    end;
+
+    Prepare_Chart(Ctx, V_Over_Second_Equiv);
+    Draw_Titration_Curve(Ctx, Problem.SType, Kx1, Kx2, Problem.Sample_Concentration, Problem.T_Concentration, Problem.Sample_Volume, V_Over_Second_Equiv, First_Guess);
+    -- Draw interesting points
+    Draw_Chart_Crosshair(Ctx, 0.0, V_Over_Second_Equiv, pH_Start);
+    Draw_Chart_Crosshair(Ctx, V_Half_First, V_Over_Second_Equiv, pH_Half_First);
+    Draw_Chart_Crosshair(Ctx, V_First_Equiv, V_Over_Second_Equiv, pH_First_Equiv);
+    Draw_Chart_Crosshair(Ctx, V_Half_Second, V_Over_Second_Equiv, pH_Half_Second);
+    Draw_Chart_Crosshair(Ctx, V_Second_Equiv, V_Over_Second_Equiv, pH_Second_Equiv);
+    Draw_Chart_Crosshair(Ctx, V_Over_Second_Equiv, V_Over_Second_Equiv, pH_Over_Second_Equiv);
+
+    Draw_Chart_Circle(Ctx, 0.0, V_Over_Second_Equiv, pH_Start_Ans);
+    Draw_Chart_Circle(Ctx, V_First_Half_Ans, V_Over_Second_Equiv, pH_First_Half_Ans);
+    Draw_Chart_Circle(Ctx, V_First_Equiv_Ans, V_Over_Second_Equiv, pH_First_Equiv_Ans);
+    Draw_Chart_Circle(Ctx, V_Second_Half_Ans, V_Over_Second_Equiv, pH_Second_Half_Ans);
+    Draw_Chart_Circle(Ctx, V_Second_Equiv_Ans, V_Over_Second_Equiv, pH_Second_Equiv_Ans);
+    Draw_Chart_Circle(Ctx, V_Over_Second_Equiv_Ans, V_Over_Second_Equiv, pH_Over_Second_Equiv_Ans);
+
+    Status_Out := Cairo.Png.Write_To_Png(Surface, Ada.Strings.Unbounded.To_String(Problem.Resource_Prefix) & TITRATION_CURVE_FILENAME);
+    Message := To_UB_Text("");
+    return Correct_Answer;
   end Check_Answer;
 
-  function Get_Assignment(Problem: in out Titration_Curve_Problem; Assignment: in out Assignment_Info.Map) return RetCode is
+  function Get_Assignment(Problem: in out Titration_Curve_Problem; Assignment: in out Assignment_Info.Map; Resource_Prefix: in String) return RetCode is
+    package FH is new Formatting_Helpers(T_Float);
+    use FH;
+
+    Sample_Conc_Int, Sample_Conc_Dec, Sample_Conc_Exp: UB_Text;
+    Sample_Volume_Int, Sample_Volume_Dec, Sample_Volume_Exp: UB_Text;
+    Titrant_Conc_Int, Titrant_Conc_Dec, Titrant_Conc_Exp: UB_Text;
+    pKx1_Int, pKx1_Dec: UB_Text;
+    pKx2_Int, pKx2_Dec: UB_Text;
   begin
-    return E_INVAL;
+    Problem.Resource_Prefix := Ada.Strings.Unbounded.To_Unbounded_String(Resource_Prefix);
+
+    Split_Integer_Decimal_Exponent_Strs(Problem.Sample_Concentration, DECIMALS, Sample_Conc_Int, Sample_Conc_Dec, Sample_Conc_Exp);
+    Split_Integer_Decimal_Exponent_Strs(Problem.Sample_Volume, DECIMALS, Sample_Volume_Int, Sample_Volume_Dec, Sample_Volume_Exp);
+    Split_Integer_Decimal_Exponent_Strs(Problem.T_Concentration, DECIMALS, Titrant_Conc_Int, Titrant_Conc_Dec, Titrant_Conc_Exp);
+    Split_Integer_Decimal_Unscaled_Strs(Problem.pKx1, DECIMALS, pKx1_Int, pKx1_Dec);
+    Split_Integer_Decimal_Unscaled_Strs(Problem.pKx2, DECIMALS, pKx2_Int, pKx2_Dec);
+
+    Assignment.Insert(PROBLEM_TYPE_KEY, PROBLEM_TYPE_TITRATION_CURVE);
+    case Problem.SType is
+      when ACID =>
+       Assignment.Insert(SAMPLE_TYPE_KEY, SAMPLE_TYPE_ACID);
+       Assignment.Insert(TITRANT_TYPE_KEY, TITRANT_TYPE_BASE);
+      when BASE =>
+       Assignment.Insert(SAMPLE_TYPE_KEY, SAMPLE_TYPE_BASE);
+       Assignment.Insert(TITRANT_TYPE_KEY, TITRANT_TYPE_ACID);
+    end case;
+    Assignment.Insert(SAMPLE_CONC_INT_KEY, UB_Text_To_Fixed_String(Sample_Conc_Int));
+    Assignment.Insert(SAMPLE_CONC_DEC_KEY, UB_Text_To_Fixed_String(Sample_Conc_Dec));
+    Assignment.Insert(SAMPLE_CONC_EXP_KEY, UB_Text_To_Fixed_String(Sample_Conc_Exp));
+    --
+    Assignment.Insert(SAMPLE_VOLUME_INT_KEY, UB_Text_To_Fixed_String(Sample_Volume_Int));
+    Assignment.Insert(SAMPLE_VOLUME_DEC_KEY, UB_Text_To_Fixed_String(Sample_Volume_Dec));
+    Assignment.Insert(SAMPLE_VOLUME_EXP_KEY, UB_Text_To_Fixed_String(Sample_Volume_Exp));
+    --
+    Assignment.Insert(TITRANT_CONC_INT_KEY, UB_Text_To_Fixed_String(Titrant_Conc_Int));
+    Assignment.Insert(TITRANT_CONC_DEC_KEY, UB_Text_To_Fixed_String(Titrant_Conc_Dec));
+    Assignment.Insert(TITRANT_CONC_EXP_KEY, UB_Text_To_Fixed_String(Titrant_Conc_Exp));
+    --
+    Assignment.Insert(PKX1_INT_KEY, UB_Text_To_Fixed_String(pKx1_Int));
+    Assignment.Insert(PKX1_DEC_KEY, UB_Text_To_Fixed_String(pKx1_Dec));
+    Assignment.Insert(PKX2_INT_KEY, UB_Text_To_Fixed_String(pKx2_Int));
+    Assignment.Insert(PKX2_DEC_KEY, UB_Text_To_Fixed_String(pKx2_Dec));
+    -- NOTE: The image will be created only when user requests to check their answer
+    Assignment.Insert(TITRATION_CURVE_IMAGE_PATH_KEY, Ada.Strings.Unbounded.To_String(Problem.Resource_Prefix) & TITRATION_CURVE_FILENAME);
+
+    return OK;
   end Get_Assignment;
 
   function Get_Parameters(Problem: in out Titration_Curve_Problem; Parameters: out Parameters_Info.Map) return RetCode is
   begin
-    return E_INVAL;
+    return OK;
   end Get_Parameters;
 
   procedure New_Problem(Problem: in out Titration_Curve_Problem) is
+    package FH is new Formatting_Helpers(T_Float);
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    package Random_Sample_Type_Gen is new Ada.Numerics.Discrete_Random(Result_Subtype => Sample_Type);
+    use FH;
+    use TFEF;
+
+    PKX_1_RANGE: constant T_Float := MAX_PKX_1 - MIN_PKX_1;
+    SAMPLE_CONCENTRATION_RANGE: constant T_Float := Log(Base => 10.0, X => MAX_SAMPLE_CONCENTRATION) - Log(Base => 10.0, X => MIN_SAMPLE_CONCENTRATION);
+    Float_Seed: Ada.Numerics.Float_Random.Generator;
+    SType_Seed: Random_Sample_Type_Gen.Generator;
   begin
-   null;
+    -- Generate sample type
+    Random_Sample_Type_Gen.Reset(Gen => SType_Seed);
+    Problem.SType := Random_Sample_Type_Gen.Random(Gen => SType_Seed);
+
+    -- Generate pKx1
+    Ada.Numerics.Float_Random.Reset(Gen => Float_Seed);
+    Problem.pKx1 := Round_To_Valid_Nums((T_Float(Ada.Numerics.Float_Random.Random(Gen => Float_Seed)) * PKX_1_RANGE + MIN_PKX_1), DECIMALS);
+
+    -- Generate pKx2
+    declare
+      MIN_PKX_2: constant T_Float := Problem.pKx1 + PKX_STEP;
+      PKX_2_RANGE: constant T_Float := MAX_PKX_2 - MIN_PKX_2;
+    begin
+      Problem.pKx2 := Round_To_Valid_Nums((T_Float(Ada.Numerics.Float_Random.Random(Gen => Float_Seed)) * PKX_2_RANGE + MIN_PKX_2), DECIMALS);
+    end;
+
+    -- Generate sample concentration
+    Problem.Sample_Concentration := Round_To_Valid_Nums((10.0 ** (T_Float(Ada.Numerics.Float_Random.Random(Gen => Float_Seed)) * SAMPLE_CONCENTRATION_RANGE + Log(Base => 10.0, X => MIN_SAMPLE_CONCENTRATION))), DECIMALS);
+    -- Generate concentration of titrimetric solution
+    declare
+      TS_MIN: constant T_Float := Problem.Sample_Concentration / 10.0;
+      TS_MAX: constant T_Float := Problem.Sample_Concentration * 10.0;
+      TS_RANGE: constant T_Float := TS_MAX - TS_MIN;
+    begin
+      Problem.T_Concentration := Round_To_Valid_Nums((T_Float(Ada.Numerics.Float_Random.Random(Gen => Float_Seed)) * TS_RANGE + TS_MIN), DECIMALS);
+    end;
+
+    -- Generate sample volume
+    declare
+      VOLUME_RANGE: constant T_Float := (MAX_VOLUME_ML - MIN_VOLUME_ML) / 1000.0;
+    begin
+      Problem.Sample_Volume := Round_To_Valid_Nums(T_Float(Ada.Numerics.Float_Random.Random(Gen => Float_Seed)) * VOLUME_RANGE + (MIN_VOLUME_ML / 1000.0), DECIMALS);
+    end;
   end New_problem;
 
   function Set_Parameters(Problem: in out Titration_Curve_Problem; Parameters: in Parameters_Info.Map) return RetCode is
   begin
-    return E_INVAL;
+    return OK;
   end Set_Parameters;
 
+  overriding procedure Finalize(Problem: in out Titration_Curve_Problem) is
+    Path: constant String := Ada.Strings.Unbounded.To_String(Problem.Resource_Prefix) & TITRATION_CURVE_FILENAME;
+    File: Ada.Text_IO.File_Type;
+  begin
+    begin
+      Ada.Text_IO.Open(File => File, Mode => Ada.Text_IO.In_File, Name => Path);
+      Ada.Text_IO.Close(File);
+      Ada.Directories.Delete_File(Path);
+    exception
+      when Ada.Text_IO.Name_Error =>
+       null;
+    end;
+  end Finalize;
+  -- END: Inherited functions
+
+  -- BEGIN: Private functions
+
+  function Calculate_1st_Diff_Acid(cA: in T_Float; Ka1: in T_Float; Ka2: in T_Float; cB: in T_Float; Kb: in T_Float; Xn: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    A: constant T_Float := -Kb;
+    B: constant T_Float := -(Kb * cB) - Kw - (Ka1 * Kb);
+    C: constant T_Float := (cA * Ka1 * Kb) + (Kb * KW) - (Ka1 * Kb * cB) - (Ka1 * KW) - (Ka1 * Ka2 * Kb);
+    D: constant T_Float := (cA * Ka1 * KW) + (2.0 * cA * Ka1 * Ka2 * Kb) + (KW * KW) + (Ka1 * Kb * KW) - (Ka1 * Ka2 * Kb * cB) - (Ka1 * Ka2 * KW);
+    E: constant T_Float := (2.0 * cA * Ka1 * Ka2 * KW) + (Ka1 * KW * KW) + (Ka1 * Ka2 * Kb * KW);
+  begin
+    return (5.0 * A * (Xn ** 4)) + (4.0 * B * (Xn ** 3)) + (3.0 * C * (Xn ** 2)) + (2.0 * D * Xn) + E;
+  end Calculate_1st_Diff_Acid;
+
+  function Calculate_2nd_Diff_Acid(cA: in T_Float; Ka1: in T_Float; Ka2: in T_Float; cB: in T_Float; Kb: in T_Float; Xn: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    A: constant T_Float := -Kb;
+    B: constant T_Float := -(Kb * cB) - Kw - (Ka1 * Kb);
+    C: constant T_Float := (cA * Ka1 * Kb) + (Kb * KW) - (Ka1 * Kb * cB) - (Ka1 * KW) - (Ka1 * Ka2 * Kb);
+    D: constant T_Float := (cA * Ka1 * KW) + (2.0 * cA * Ka1 * Ka2 * Kb) + (KW * KW) + (Ka1 * Kb * KW) - (Ka1 * Ka2 * Kb * cB) - (Ka1 * Ka2 * KW);
+  begin
+    return (20.0 * A * (Xn ** 3)) + (12.0 * B * (Xn ** 2)) + (6.0 * C * Xn) + (2.0 * D);
+  end Calculate_2nd_Diff_Acid;
+
+  function Calculate_1st_Diff_Base(cB: in T_Float; Kb1: in T_Float; Kb2: in T_Float; cA: in T_Float; Ka: in T_Float; Xn: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    A: constant T_Float := -(Kb1 * Kb2);
+    B: constant T_Float := -(2.0 * cB * Kb1 * Kb2) - (Kb1 * KW) - (Ka * Kb1 * Kb2);
+    C: constant T_Float := (cA * Ka * Kb1 * Kb2) + (Kb1 * Kb2 * KW) - (cB * KB1 * KW) - (2.0 * cB * Ka * Kb1 * Kb2) - (KW ** 2) - (Ka * Kb1 * KW);
+    D: constant T_Float := (cA * Ka * Kb1 * KW) + (Kb1 * (KW ** 2)) + (Ka * Kb1 * Kb2 * KW) - (cB * Ka * Kb1 * KW) - (Ka * (KW ** 2));
+    E: constant T_Float := (cA * Ka * (KW ** 2)) + (Ka * Kb1 * (KW ** 2)) +  (KW ** 3);
+  begin
+    return (5.0 * A * (Xn ** 4)) + (4.0 * B * (Xn ** 3)) + (3.0 * C * (Xn ** 2)) + (2.0 * D * Xn) + E;
+  end Calculate_1st_Diff_Base;
+
+  function Calculate_2nd_Diff_Base(cB: in T_Float; Kb1: in T_Float; Kb2: in T_Float; cA: in T_Float; Ka: in T_Float; Xn: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    A: constant T_Float := -(Kb1 * Kb2);
+    B: constant T_Float := -(2.0 * cB * Kb1 * Kb2) - (Kb1 * KW) - (Ka * Kb1 * Kb2);
+    C: constant T_Float := (cA * Ka * Kb1 * Kb2) + (Kb1 * Kb2 * KW) - (cB * KB1 * KW) - (2.0 * cB * Ka * Kb1 * Kb2) - (KW ** 2) - (Ka * Kb1 * KW);
+    D: constant T_Float := (cA * Ka * Kb1 * KW) + (Kb1 * (KW ** 2)) + (Ka * Kb1 * Kb2 * KW) - (cB * Ka * Kb1 * KW) - (Ka * (KW ** 2));
+  begin
+    return (20.0 * A * (Xn ** 3)) + (12.0 * B * (Xn ** 2)) + (6.0 * C * Xn) + (2.0 * D);
+  end Calculate_2nd_Diff_Base;
+
+  function Calculate_Full_Acid(cA: in T_Float; Ka1: in T_Float; Ka2: in T_Float; cB: in T_Float; Kb: in T_Float; Xn: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    A: constant T_Float := -Kb;
+    B: constant T_Float := -(Kb * cB) - KW - (Ka1 * Kb);
+    C: constant T_Float := (cA * Ka1 * Kb) + (Kb * KW) - (Ka1 * Kb * cB) - (Ka1 * KW) - (Ka1 * Ka2 * Kb);
+    D: constant T_Float := (cA * Ka1 * KW) + (2.0 * cA * Ka1 * Ka2 * Kb) + (KW * KW) + (Ka1 * Kb * KW) - (Ka1 * Ka2 * Kb * cB) - (Ka1 * Ka2 * KW);
+    E: constant T_Float := (2.0 * cA * Ka1 * Ka2 * KW) + (Ka1 * KW * KW) + (Ka1 * Ka2 * Kb * KW);
+    F: constant T_Float := Ka1 * Ka2 * KW * KW;
+  begin
+    return (A * (Xn ** 5)) + (B * (Xn ** 4)) + (C * (Xn ** 3)) + (D * (Xn ** 2)) + (E * Xn) + F;
+  end Calculate_Full_Acid;
+
+  function Calculate_Full_Base(cB: in T_Float; Kb1: in T_Float; Kb2: in T_Float; cA: in T_Float; Ka: in T_Float; Xn: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    A: constant T_Float := -(Kb1 * Kb2);
+    B: constant T_Float := -(2.0 * cB * Kb1 * Kb2) - (Kb1 * KW) - (Ka * Kb1 * Kb2);
+    C: constant T_Float := (cA * Ka * Kb1 * Kb2) + (KW ** 3) + (Kb1 * Kb2 * KW) - (cB * Kb1 * KW) - (2.0 * cB * Ka * Kb1 * Kb2) - (KW ** 2) - (Ka * Kb1 * KW);
+    D: constant T_Float := (cA * Ka * Kb1 * KW) + (Kb1 * (KW ** 2)) + (Ka * Kb1 * Kb2 * KW) - (cB * Ka * Kb1 * KW) - (Ka * (KW ** 2));
+    E: constant T_Float := (cA * Ka * (KW ** 2)) + (Ka * Kb1 * KW * KW) +  (KW * KW * KW);
+    F: constant T_Float := Ka * KW * KW * KW;
+  begin
+    return (A * (Xn ** 5)) + (B * (Xn ** 4)) + (C * (Xn ** 3)) + (D * (Xn ** 2)) + (E * Xn) + F;
+  end Calculate_Full_Base;
+
+  function Calculate_Target_End_Value(Num: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    E: T_Float;
+    Scaled: T_Float;
+  begin
+    E := Log(Base => 10.0, X => Num);
+    E := T_Float'Floor(E);
+    E := 10.0 ** E;
+    Scaled := Num / E;
+    Scaled := T_Float'Ceiling(Scaled);
+
+    return Scaled * E;
+  end Calculate_Target_End_Value;
+
+  procedure Draw_Chart_Circle(Ctx: in out Cairo.Cairo_Context; X_Raw: in T_Float; X_Range_Raw: in T_Float; Y_Raw: in T_Float; Y_Range_Raw: in T_Float := 14.0) is
+    X: constant Glib.GDouble := Glib.GDouble(X_Raw * T_Float(IMAGE_CHART_WIDTH) / X_Range_Raw) + X_OFFSET;
+    Y: constant Glib.GDouble := Glib.GDouble(T_Float(IMAGE_CHART_HEIGHT) - (Y_Raw * T_Float(IMAGE_CHART_HEIGHT) / Y_Range_Raw)) + Y_OFFSET;
+  begin
+    Cairo.Set_Source_Rgb(Ctx, 0.0, 0.0, 0.7);
+    Cairo.Set_Line_Width(Ctx, CROSSHAIR_THICKNESS);
+    Cairo.Move_To(Ctx, X, Y);
+    Cairo.Arc(Ctx, X, Y, 5.0, 0.0, Glib.GDouble(Ada.Numerics.PI * 2.0));
+    Cairo.Stroke(Ctx);
+  end Draw_Chart_Circle;
+
+  procedure Draw_Chart_Crosshair(Ctx: in out Cairo.Cairo_Context; X_Raw: in T_Float; X_Range_Raw: in T_Float; Y_Raw: in T_Float; Y_Range_Raw: in T_Float := 14.0) is
+    XV: constant Glib.GDouble := Glib.GDouble(X_Raw * T_Float(IMAGE_CHART_WIDTH) / X_Range_Raw) + X_OFFSET;
+    YV: constant Glib.GDouble := Glib.GDouble(T_Float(IMAGE_CHART_HEIGHT) - (Y_Raw * T_Float(IMAGE_CHART_HEIGHT) / Y_Range_Raw)) + Y_OFFSET - (CROSSHAIR_LENGTH / 2.0);
+    XH: constant Glib.GDouble := Glib.GDouble(X_Raw * T_Float(IMAGE_CHART_WIDTH) / X_Range_Raw) + X_OFFSET - (CROSSHAIR_LENGTH / 2.0);
+    YH: constant Glib.GDouble := Glib.GDouble(T_Float(IMAGE_CHART_HEIGHT) - (Y_Raw * T_Float(IMAGE_CHART_HEIGHT) / Y_Range_Raw)) + Y_OFFSET;
+  begin
+    Cairo.Set_Source_Rgb(Ctx, 0.0, 0.7, 0.0);
+    Cairo.Set_Line_Width(Ctx, CROSSHAIR_THICKNESS);
+    Cairo.Move_To(Ctx, XV, YV);
+    Cairo.Line_To(Ctx, XV, YV + CROSSHAIR_LENGTH);
+    Cairo.Move_To(Ctx, XH, YH);
+    Cairo.Line_To(Ctx, XH + CROSSHAIR_LENGTH, YH);
+    Cairo.Stroke(Ctx);
+  end Draw_Chart_Crosshair;
+
+  procedure Draw_Titration_Curve(Ctx: in out Cairo.Cairo_Context; SType: in Sample_Type; Kx1: in T_Float; Kx2: in T_Float; c0_Sample: in T_Float; c0_TS: in T_Float; V_Sample: in T_Float; V_Final: in T_Float; First_Guess: in T_Float) is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    V_Step: constant T_Float := V_Final / 800.0;
+    V_TS_Now: T_Float;
+    V_Total_Now: T_Float;
+    Nothing_Drawn: Boolean := True;
+    c_H3O: T_Float := First_Guess;
+    Kz: T_Float;
+  begin
+    Cairo.Set_Source_Rgb(Ctx, 1.0, 0.0, 0.0);
+    Cairo.Set_Line_Width(Ctx, TC_LINE_THICKNESS);
+
+    case SType is
+      when ACID =>
+       Kz := TITR_BASE_KB;
+       -- Calculate acid titration curve from the starting point
+       V_TS_Now := 0.0;
+       V_Total_Now := V_Sample;
+      when BASE =>
+       Kz := TITR_ACID_KA;
+       -- Calculate base titration curve from the ending point
+       V_TS_Now := V_Final;
+       V_Total_Now := V_Sample + V_Final;
+    end case;
+
+    loop
+      declare
+       c_TS: constant T_Float := c0_TS * V_TS_Now / V_Total_Now;
+       c_Sample: constant T_Float := c0_Sample * V_Sample / V_Total_Now;
+       pH: T_Float;
+      begin
+        c_H3O := Solve_Halleys_Approximation(SType, c_Sample, Kx1, Kx2, c_TS, Kz, c_H3O);
+       pH := -Log(Base => 10.0, X => c_H3O);
+       if Nothing_Drawn then
+         Cairo.Move_To(Ctx, (Glib.GDouble(V_TS_Now) * IMAGE_CHART_WIDTH) / Glib.GDouble(V_Final) + X_OFFSET, (Glib.GDouble(T_Float(IMAGE_CHART_HEIGHT) - (pH * T_Float(IMAGE_CHART_HEIGHT) / 14.0))) + Y_OFFSET);
+         Nothing_Drawn := False;
+       else
+         Draw_Titration_Curve_Point(Ctx, V_TS_Now, pH, V_Final);
+       end if;
+
+       --Ada.Text_IO.Put_Line("pH = " & T_Float'Image(pH) & " , V = " & T_Float'Image(V_TS_Now) & " c_Sample = " & T_Float'Image(c_Sample) & " , c_TS = " & T_Float'Image(c_TS));
+
+       case SType is
+         when ACID =>
+           V_TS_Now := V_TS_Now + V_Step;
+           V_Total_Now := V_Total_Now + V_Step;
+           exit when V_TS_Now >= V_Final;
+         when BASE =>
+           V_TS_Now := V_TS_Now - V_Step;
+           V_Total_Now := V_Total_Now - V_Step;
+           exit when V_TS_Now <= 0.0;
+       end case;
+      end;
+    end loop;
+    Cairo.Stroke(Ctx);
+
+  end Draw_Titration_Curve;
+
+  procedure Draw_Titration_Curve_Point(Ctx: in out Cairo.Cairo_Context; X_Raw: in T_Float; Y_Raw: in T_Float; X_Range_Raw: in T_Float; Y_Range_Raw: in T_Float := 14.0) is
+    X: constant Glib.GDouble := Glib.GDouble(X_Raw * T_Float(IMAGE_CHART_WIDTH) / X_Range_Raw) + X_OFFSET;
+    Y: constant Glib.GDouble := Glib.GDouble(T_Float(IMAGE_CHART_HEIGHT) - (Y_Raw * T_Float(IMAGE_CHART_HEIGHT) / Y_Range_Raw)) + Y_OFFSET;
+  begin
+    Cairo.Line_To(Ctx, X, Y);
+    Cairo.Move_To(Ctx, X, Y);
+  end Draw_Titration_Curve_Point;
+
+  procedure Prepare_Chart(Ctx: in out Cairo.Cairo_Context; V_Final: in T_Float) is
+    package TFIO is new Ada.Text_IO.Float_IO(T_Float);
+    use Interfaces.C.Strings;
+
+    Y_Step: constant Glib.GDouble := IMAGE_CHART_HEIGHT / 14.0;
+  begin
+    Cairo.Set_Font_Size(Ctx, FONT_SIZE);
+    -- Draw background
+    Cairo.Set_Source_Rgb(Ctx, 1.0, 1.0, 1.0);
+    Cairo.Rectangle(Ctx, 0.0, 0.0, Glib.GDouble(IMAGE_WIDTH), Glib.GDouble(IMAGE_HEIGHT));
+    Cairo.Stroke_Preserve(Ctx);
+    Cairo.Fill(Ctx);
+
+    -- Draw legend
+    Cairo.Set_Source_Rgb(Ctx, 0.0, 0.0, 0.7);
+    Cairo.Set_Line_Width(Ctx, CROSSHAIR_THICKNESS);
+    Cairo.Move_To(Ctx, LEGEND_X_USER_COORD, LEGEND_Y_COORD);
+    Cairo.Arc(Ctx, LEGEND_X_USER_COORD, LEGEND_Y_COORD, 5.0, 0.0, Glib.GDouble(Ada.Numerics.PI * 2.0));
+    Cairo.Stroke(Ctx);
+    Cairo.Set_Source_Rgb(Ctx, 0.0, 0.0, 0.0);
+    Cairo.Move_To(Ctx, LEGEND_X_USER_COORD + LEGEND_MARK_TEXT_X_OFFSET, LEGEND_Y_TEXT_COORD);
+    Cairo.Show_Text(Ctx, "Vaše odpověď");
+    Cairo.Stroke(Ctx);
+    --
+    Cairo.Set_Source_Rgb(Ctx, 0.0, 0.7, 0.0);
+    Cairo.Move_To(Ctx, LEGEND_X_CALCD_COORD - (CROSSHAIR_LENGTH / 2.0), LEGEND_Y_COORD);
+    Cairo.Line_To(Ctx, LEGEND_X_CALCD_COORD + (CROSSHAIR_LENGTH / 2.0), LEGEND_Y_COORD);
+    Cairo.Move_To(Ctx, LEGEND_X_CALCD_COORD, LEGEND_Y_COORD - (CROSSHAIR_LENGTH / 2.0));
+    Cairo.Line_To(Ctx, LEGEND_X_CALCD_COORD, LEGEND_Y_COORD + (CROSSHAIR_LENGTH / 2.0));
+    Cairo.Stroke(Ctx);
+    Cairo.Set_Source_Rgb(Ctx, 0.0, 0.0, 0.0);
+    Cairo.Move_To(Ctx, LEGEND_X_CALCD_COORD + LEGEND_MARK_TEXT_X_OFFSET, LEGEND_Y_TEXT_COORD);
+    Cairo.Show_Text(Ctx, "Vypočtená odpoveď");
+    Cairo.Stroke(Ctx);
+
+    -- Set line properties
+    Cairo.Set_Source_Rgb(Ctx, 0.0, 0.0, 0.0);
+    Cairo.Set_Line_Width(Ctx, AXIS_LINE_THICKNESS);
+    Cairo.Set_Font_Size(Ctx, FONT_SIZE);
+    -- Draw X-axis line
+    Cairo.Move_To(Ctx, X_OFFSET, IMAGE_CHART_HEIGHT + Y_OFFSET);
+    Cairo.Line_To(Ctx, Glib.GDouble(IMAGE_WIDTH) - IMAGE_RIGHT_BORDER_WIDTH, IMAGE_CHART_HEIGHT + Y_OFFSET);
+    -- Draw Y-axis line
+    Cairo.Move_To(Ctx, X_OFFSET, Y_OFFSET - (AXIS_LINE_THICKNESS / 2.0));
+    Cairo.Line_To(Ctx, X_OFFSET, IMAGE_CHART_HEIGHT + Y_OFFSET);
+
+    -- X-axis ticks and labels
+    declare
+      Real_End_Value: constant T_Float := V_Final * 1000.0;
+      Target_End_Value: constant T_Float := Calculate_Target_End_Value(V_Final * 1000.0);
+      Tick_Step: Glib.GDouble := Glib.GDouble(Target_End_Value);
+      R_T_Ratio: constant Glib.GDouble := Glib.GDouble(Target_End_Value / Real_End_value);
+      Divide_By: Glib.GDouble := 2.0;
+      Tick_Step_Pixels: Glib.GDouble;
+    begin
+      --Ada.Text_IO.Put("TEV [mL]: "); TFIO.Put(Target_End_Value); Ada.Text_IO.New_Line; 
+
+      Tick_Step_Pixels := IMAGE_CHART_WIDTH / Tick_Step * R_T_Ratio;
+      while Tick_Step_Pixels < BIG_TICK_X_MIN_STEP loop
+       Tick_Step := Tick_Step / Divide_By;
+       if Divide_By = 2.0 then
+         Divide_By := 5.0;
+       else
+         Divide_By := 2.0;
+       end if;
+       Tick_Step_Pixels := IMAGE_CHART_WIDTH / Tick_Step * R_T_Ratio;
+      end loop;
+
+      --Ada.Text_IO.Put("X tick step [mL]: "); TFIO.Put(Target_End_Value / T_Float(Tick_Step)); Ada.Text_IO.Put(" | pixels: "); TFIO.Put(T_Float(Tick_Step_Pixels)); Ada.Text_IO.New_Line;
+
+      -- Draw ticks and labels
+      declare
+       Tick_Label_Step: constant Integer := Integer(Target_End_Value / T_Float(Tick_Step));
+       Tick_X_Pos: Glib.GDouble := X_OFFSET;
+       Tick_Label: Integer := 0;
+       Text_Extents: aliased Cairo.Cairo_Text_Extents;
+      begin
+       while Tick_X_Pos < IMAGE_CHART_WIDTH + X_OFFSET loop
+         declare
+           Tick_Label_CStrPtr: Interfaces.C.Strings.chars_ptr := Interfaces.C.Strings.New_Char_Array(Interfaces.C.To_C(Integer'Image(Tick_Label)));
+         begin
+           if Tick_Label_CStrPtr = Interfaces.C.Strings.Null_Ptr then
+             goto Skip_X_Tick;
+           end if;
+
+           -- Draw tick
+           Cairo.Move_To(Ctx, Tick_X_Pos, IMAGE_CHART_HEIGHT + Y_OFFSET);
+           Cairo.Line_To(Ctx, Tick_X_Pos, IMAGE_CHART_HEIGHT + BIG_TICK_LENGTH + Y_OFFSET);
+
+           -- Draw label
+           Cairo.Text_Extents(Ctx, Tick_Label_CStrPtr, Text_Extents'Access);
+           Cairo.Move_To(Ctx, Tick_X_Pos - (Text_Extents.X_Advance / 2.0), IMAGE_CHART_HEIGHT + BIG_TICK_LENGTH + Y_OFFSET + Text_Extents.Height + TEXT_SPACE);
+           Cairo.Show_Text(Ctx, Integer'Image(Tick_Label));
+           Interfaces.C.Strings.Free(Tick_Label_CStrPtr);
+
+           <<Skip_X_Tick>>
+           Tick_X_Pos := Tick_X_Pos + Tick_Step_Pixels;
+           Tick_Label := Tick_Label + Tick_Label_Step;
+         end;
+       end loop;
+      end;
+    end;
+
+    -- Y-axis ticks and labels
+    for Idx in 0 .. 14 loop
+      declare
+        Tick_Y_Pos: constant Glib.GDouble := Glib.GDouble(Idx) * Y_Step + Y_OFFSET;
+       Tick_Label_CStrPtr: Interfaces.C.Strings.chars_ptr;
+       Text_Extents: aliased Cairo.Cairo_Text_Extents;
+      begin
+       -- Draw tick
+       Cairo.Move_To(Ctx, X_OFFSET - BIG_TICK_LENGTH, Tick_Y_Pos);
+       Cairo.Line_To(Ctx, X_OFFSET, Tick_Y_Pos);
+
+       -- Draw label   
+       Tick_Label_CStrPtr := Interfaces.C.Strings.New_Char_Array(Interfaces.C.To_C(Integer'Image(14 - Idx)));
+       if Tick_Label_CStrPtr /= Interfaces.C.Strings.Null_Ptr then
+         Cairo.Text_Extents(Ctx, Tick_Label_CStrPtr, Text_Extents'Access);
+         Cairo.Move_To(Ctx, X_OFFSET - Text_Extents.X_Advance - BIG_TICK_LENGTH - TEXT_SPACE, Tick_Y_Pos + (Text_Extents.Height / 2.0));
+         Cairo.Show_Text(Ctx, Integer'Image(14 - Idx));
+         Interfaces.C.Strings.Free(Tick_Label_CStrPtr);
+       end if;
+      end;
+    end loop;
+
+    -- Draw axis units
+    declare
+      Tick_Label_CStrPtr: Interfaces.C.Strings.chars_ptr;
+      Text_Extents: aliased Cairo.Cairo_Text_Extents;
+    begin
+      -- X-axis
+      Tick_Label_CStrPtr := Interfaces.C.Strings.New_Char_Array(Interfaces.C.To_C(X_AXIS_UNITS_TEXT));
+      if Tick_Label_CStrPtr /= Interfaces.C.Strings.Null_Ptr then
+       Cairo.Text_Extents(Ctx, Tick_Label_CStrPtr, Text_Extents'Access);
+       Cairo.Move_To(Ctx, X_OFFSET + (IMAGE_CHART_WIDTH / 2.0) - (Text_Extents.Width / 2.0), Glib.GDouble(IMAGE_HEIGHT) - TEXT_SPACE);
+       Cairo.Show_Text(Ctx, X_AXIS_UNITS_TEXT);
+       Interfaces.C.Strings.Free(Tick_Label_CStrPtr);
+      end if;
+
+      -- Y-axis
+      Tick_Label_CStrPtr := Interfaces.C.Strings.New_Char_Array(Interfaces.C.To_C(Y_AXIS_UNITS_TEXT));
+      if Tick_Label_CStrPtr /= Interfaces.C.Strings.Null_Ptr then
+       Cairo.Text_Extents(Ctx, Tick_Label_CStrPtr, Text_Extents'Access);
+        Cairo.Move_To(Ctx, TEXT_SPACE + Text_Extents.Height, Y_OFFSET + IMAGE_CHART_HEIGHT / (2.0) - (Text_Extents.X_Advance / 2.0));
+       Cairo.Save(Ctx);
+       Cairo.Rotate(Ctx, -(Ada.Numerics.PI / 2.0));
+       Cairo.Show_Text(Ctx, Y_AXIS_UNITS_TEXT);
+       Cairo.Restore(Ctx);
+       Interfaces.C.Strings.Free(Tick_Label_CStrPtr);
+      end if;
+    end;
+
+    Cairo.Stroke(Ctx);
+  end Prepare_Chart;
+
+  function Solve_Halleys_Approximation(SType: in Sample_Type; c_Sample: in T_Float; Kx1: in T_Float; Kx2: in T_Float; c_TS: in T_Float; Kz: in T_Float; First_Guess: in T_Float) return T_Float is
+    package TFEF is new Ada.Numerics.Generic_Elementary_Functions(T_Float);
+    use TFEF;
+
+    Xn, Xnp, I_Frac: T_Float;
+    Difference: T_Float;
+    Ctr: Positive := 1;
+  begin
+    Xn := First_Guess;
+
+   -- Ada.Text_IO.Put_Line("First guess: " & T_Float'Image(Xn));
+    loop
+      declare
+       vf: T_Float;
+       vfd: T_Float;
+       vfdd: T_Float;
+      begin
+       case SType is
+         when ACID =>
+           vf := Calculate_Full_Acid(c_Sample, Kx1, Kx2, c_TS, Kz, Xn);
+           vfd := Calculate_1st_Diff_Acid(c_Sample, Kx1, Kx2, c_TS, Kz, Xn);
+           vfdd := Calculate_2nd_Diff_Acid(c_Sample, Kx1, Kx2, c_TS, Kz, Xn);
+         when BASE =>
+           vf := Calculate_Full_Base(c_Sample, Kx1, Kx2, c_TS, Kz, Xn);
+           vfd := Calculate_1st_Diff_Base(c_Sample, Kx1, Kx2, c_TS, Kz, Xn);
+           vfdd := Calculate_2nd_Diff_Base(c_Sample, Kx1, Kx2, c_TS, Kz, Xn);
+       end case;
+       I_Frac := (2.0 * vf * vfd) / ((2.0 * (vfd ** 2)) - (vf * vfdd));
+      end;
+
+      Xnp := Xn - I_Frac;
+      --Ada.Text_IO.Put_Line("Ctr = " & Positive'Image(Ctr) & ", Xn = " & T_Float'Image(Xn) & " , Xnp = " & T_Float'Image(Xnp));
+      Difference := Abs(Log(Base => 10.0, X => Xnp) - Log(Base => 10.0, X => Xn));
+      exit when Difference < 0.001;
+      Ctr := Ctr + 1;
+      Xn := Xnp;
+    end loop;
+
+    --Ada.Text_IO.Put_Line("Result: " & T_Float'Image(Xnp) & ", iters: " & Positive'Image(Ctr));
+    return Xnp;
+  end Solve_Halleys_Approximation;
+
 end Titration_Curve_Suite;
index ab646d281e0c8dd12a046108f5a4de98e358086f..3ad5e7d2e46b24571769e61fe2c6ad73ffcb2b9e 100644 (file)
@@ -1,15 +1,18 @@
 with Global_Types;
 with Problem_Generator_Syswides;
 with Ada.Finalization;
+with Ada.Strings.Unbounded;
+with Cairo;
+with Glib;
 
 use Global_Types;
 use Problem_Generator_Syswides;
 package Problem_Generator is
-  type Chem_Problem is abstract tagged limited private;
+  type Chem_Problem is abstract limited new Ada.Finalization.Limited_Controlled with private;
 
   function Create return access Chem_Problem is abstract;
   function Check_Answer(Problem: in out Chem_Problem; Answer: in Answer_Info.Map; Message: out UB_Text) return Answer_RetCode is abstract;
-  function Get_Assignment(Problem: in out Chem_Problem; Assignment: in out Assignment_Info.Map) return RetCode is abstract;
+  function Get_Assignment(Problem: in out Chem_Problem; Assignment: in out Assignment_Info.Map; Resource_Prefix: in String) return RetCode is abstract;
   function Get_Parameters(Problem: in out Chem_Problem; Parameters: out Parameters_Info.Map) return RetCode is abstract;
   procedure New_Problem(Problem: in out Chem_Problem) is abstract;
   function Set_Parameters(Problem: in out Chem_Problem; Parameters: in Parameters_Info.Map) return RetCode is abstract;
@@ -17,7 +20,7 @@ package Problem_Generator is
   function Get_Problem(P_Type: in Problem_Type) return access Chem_Problem'Class;
 
 private
-  type Chem_Problem is abstract tagged limited
+  type Chem_Problem is abstract limited new Ada.Finalization.Limited_Controlled with
     record
       Mutex: aliased Simple_Mutex;
     end record;
@@ -45,7 +48,7 @@ private
       -- Inherited
       function Check_Answer(Problem: in out Acidobazic_Problem; Answer: in Answer_Info.Map; Message: out UB_Text) return Answer_RetCode;
       procedure New_Problem(Problem: in out Acidobazic_Problem);
-      function Get_Assignment(Problem: in out Acidobazic_Problem; Assignment: in out Assignment_Info.Map) return RetCode;
+      function Get_Assignment(Problem: in out Acidobazic_Problem; Assignment: in out Assignment_Info.Map; Resource_Prefix: in String) return RetCode;
       function Get_Parameters(Problem: in out Acidobazic_Problem; Parameters: out Parameters_Info.Map) return RetCode;
       function Set_Parameters(Problem: in out Acidobazic_Problem; Parameters: in Parameters_Info.Map) return RetCode;
 
@@ -107,7 +110,7 @@ private
     -- Inherited
     function Check_Answer(Problem: in out Solubility_Problem; Answer: in Answer_Info.Map; Message: out UB_Text) return Answer_RetCode;
     procedure New_Problem(Problem: in out Solubility_Problem);
-    function Get_Assignment(Problem: in out Solubility_Problem; Assignment: in out Assignment_Info.Map) return RetCode;
+    function Get_Assignment(Problem: in out Solubility_Problem; Assignment: in out Assignment_Info.Map; Resource_Prefix: in String) return RetCode;
     function Get_Parameters(Problem: in out Solubility_Problem; Parameters: out Parameters_Info.Map) return RetCode;
     function Set_Parameters(Problem: in out Solubility_Problem; Parameters: in Parameters_Info.Map) return RetCode;
 
@@ -185,21 +188,92 @@ private
 
   package Titration_Curve_Suite is
     use Problem_Generator_Syswides.Titration_Curve_Suite;
+    use Glib;
 
     type Titration_Curve_Problem is new Problem_Generator.Chem_Problem with private;
     -- Constructor
     function Create return access Titration_Curve_Problem;
     -- Inherited
     function Check_Answer(Problem: in out Titration_Curve_Problem; Answer: in Answer_Info.Map; Message: out UB_Text) return Answer_RetCode;
-    function Get_Assignment(Problem: in out Titration_Curve_Problem; Assignment: in out Assignment_Info.Map) return RetCode;
+    function Get_Assignment(Problem: in out Titration_Curve_Problem; Assignment: in out Assignment_Info.Map; Resource_Prefix: in String) return RetCode;
     function Get_Parameters(Problem: in out Titration_Curve_Problem; Parameters: out Parameters_Info.Map) return RetCode;
     procedure New_Problem(Problem: in out Titration_Curve_Problem);
     function Set_Parameters(Problem: in out Titration_Curve_Problem; Parameters: in Parameters_Info.Map) return RetCode;
+    overriding procedure Finalize(Problem: in out Titration_Curve_Problem);
 
   private
+    Math_Limitation: exception;
     type T_Float is digits 15;
 
-    type Titration_Curve_Problem is new Problem_Generator.Chem_Problem with null record;
+    type Titration_Curve_Problem is new Problem_Generator.Chem_Problem with
+      record
+       SType: Sample_Type;
+       pKx1: T_Float;
+       pKx2: T_Float;
+       Resource_Prefix: Ada.Strings.Unbounded.Unbounded_String;
+       Sample_Volume: T_Float;
+       Sample_Concentration: T_Float;
+       T_Concentration: T_Float;
+      end record;
+
+    function Calculate_1st_Diff_Acid(cA: in T_Float; Ka1: in T_Float; Ka2: in T_Float; cB: in T_Float; Kb: in T_Float; Xn: in T_Float) return T_Float;
+    function Calculate_2nd_Diff_Acid(cA: in T_Float; Ka1: in T_Float; Ka2: in T_Float; cB: in T_Float; Kb: in T_Float; Xn: in T_Float) return T_Float;
+    function Calculate_1st_Diff_Base(cB: in T_Float; Kb1: in T_Float; Kb2: in T_Float; cA: in T_Float; Ka: in T_Float; Xn: in T_Float) return T_Float;
+    function Calculate_2nd_Diff_Base(cB: in T_Float; Kb1: in T_Float; Kb2: in T_Float; cA: in T_Float; Ka: in T_Float; Xn: in T_Float) return T_Float;
+    function Calculate_Full_Acid(cA: in T_Float; Ka1: in T_Float; Ka2: in T_Float; cB: in T_Float; Kb: in T_Float; Xn: in T_Float) return T_Float;
+    function Calculate_Full_Base(cB: in T_Float; Kb1: in T_Float; Kb2: in T_Float; cA: in T_Float; Ka: in T_Float; Xn: in T_Float) return T_Float;
+    function Calculate_Target_End_Value(Num: in T_Float) return T_Float;
+    procedure Draw_Chart_Circle(Ctx: in out Cairo.Cairo_Context; X_Raw: in T_Float; X_Range_Raw: in T_Float; Y_Raw: in T_Float; Y_Range_Raw: in T_Float := 14.0);
+    procedure Draw_Chart_Crosshair(Ctx: in out Cairo.Cairo_Context; X_Raw: in T_Float; X_Range_Raw: in T_Float; Y_Raw: in T_Float; Y_Range_Raw: in T_Float := 14.0);
+    procedure Draw_Titration_Curve(Ctx: in out Cairo.Cairo_Context; SType: in Sample_Type; Kx1: in T_Float; Kx2: in T_Float; c0_Sample: in T_Float; c0_TS: in T_Float; V_Sample: in T_Float; V_Final: in T_Float; First_Guess: in T_Float);
+    procedure Draw_Titration_Curve_Point(Ctx: in out Cairo.Cairo_Context; X_Raw: in T_Float; Y_Raw: in T_Float; X_Range_Raw: in T_Float; Y_Range_Raw: in T_Float := 14.0);
+    procedure Prepare_Chart(Ctx: in out Cairo.Cairo_Context; V_Final: in T_Float);
+    function Solve_Halleys_Approximation(SType: in Sample_Type; c_Sample: in T_Float; Kx1: in T_Float; Kx2: in T_Float; c_TS: in T_Float; Kz: in T_Float; First_Guess: in T_Float) return T_Float;
+
+    MIN_PKX_1: constant T_Float := 2.0;
+    MAX_PKX_1: constant T_Float := 4.2;
+    MAX_PKX_2: constant T_Float := 8.3;
+    PKX_STEP: constant T_Float := 4.0;
+    MIN_VOLUME_ML: constant T_Float := 5.0;
+    MAX_VOLUME_ML: constant T_Float := 60.0;
+    MIN_SAMPLE_CONCENTRATION: constant T_Float := 1.0E-3;
+    MAX_SAMPLE_CONCENTRATION: constant T_Float := 1.0;
+    --
+    TITR_ACID_KA: constant T_Float := 0.0001; -- Corresponds to HCl
+    --TITR_BASE_KB: constant T_Float := 0.631; -- Corresponds to NaOH
+    TITR_BASE_KB: constant T_Float := 0.0001; -- Corresponds to NaOH
+    KW: constant T_Float := 1.0E-14;
+    --
+    DECIMALS: constant T_Float := 1.0E3;
+    MAX_DIFFERENCE: constant := 3.0E-14;
+    --
+    IMAGE_WIDTH: constant Glib.GInt := 800;
+    IMAGE_HEIGHT: constant Glib.GInt := 800;
+    IMAGE_RIGHT_BORDER_WIDTH: constant Glib.GDouble := 35.0;
+    IMAGE_BOTTOM_BORDER_HEIGHT: constant Glib.GDouble := 45.0;
+    X_OFFSET: constant Glib.GDouble := 45.0;
+    Y_OFFSET: constant Glib.GDouble := 60.0;
+    IMAGE_CHART_WIDTH: constant Glib.GDouble := Glib.GDouble(IMAGE_WIDTH) - X_OFFSET - IMAGE_RIGHT_BORDER_WIDTH;
+    IMAGE_CHART_HEIGHT: constant Glib.GDouble := Glib.GDouble(IMAGE_HEIGHT) - Y_OFFSET - IMAGE_BOTTOM_BORDER_HEIGHT;
+    CROSSHAIR_LENGTH: constant Glib.GDouble := 12.0;
+    BIG_TICK_LENGTH: constant Glib.GDouble := 8.0;
+    BIG_TICK_X_MIN_STEP: constant Glib.GDouble := 40.0;
+    AXIS_LINE_THICKNESS: constant Glib.GDouble := 3.0;
+    CROSSHAIR_THICKNESS: constant Glib.GDouble := 1.0;
+    TC_LINE_THICKNESS: constant Glib.GDouble := 2.0;
+    FONT_SIZE: constant Glib.GDouble := 18.0;
+    TEXT_SPACE: constant Glib.GDouble := 5.0;
+    --
+    LEGEND_Y_COORD: constant Glib.GDouble := 30.0;
+    LEGEND_Y_TEXT_COORD: constant Glib.GDouble := 38.0;
+    LEGEND_X_USER_COORD: constant Glib.GDouble := 100.0;
+    LEGEND_X_CALCD_COORD: constant Glib.GDouble := 500.0;
+    LEGEND_MARK_TEXT_X_OFFSET: constant Glib.GDouble := 30.0;
+    --
+    X_AXIS_UNITS_TEXT: constant String := "V (odměrný roztok) [mL]";
+    Y_AXIS_UNITS_TEXT: constant String := "pH";
+    --
+    TITRATION_CURVE_FILENAME: constant String := "-curve.png";
 
   end Titration_Curve_Suite;
 
index b6b238ed5c03046e66102a5b2fee8dc8633155e5..9caa4ada8cabe61872bf964d9f40c07529be7a54 100644 (file)
@@ -20,6 +20,7 @@ package Problem_Generator_Syswides is
   PROBLEM_TYPE_KEY: constant String := "PROBLEM_TYPE";
   PROBLEM_TYPE_ACIDOBAZIC: constant String := Problem_Type'Image(Acidobazic);
   PROBLEM_TYPE_SOLUBILITY: constant String := Problem_Type'Image(Solubility);
+  PROBLEM_TYPE_TITRATION_CURVE: constant String := Problem_Type'Image(Titration_Curve);
   --
   RESERVED_PROBLEM_ID_KEY: constant String := "RESERVED__PROBLEM_ID";
     RESERVED_PROBLEM_ID_VAL_KEY: constant String := "RESERVED__PROBLEM_ID_VAL";
@@ -81,7 +82,77 @@ package Problem_Generator_Syswides is
   end Solubility_Suite;
 
   package Titration_Curve_Suite is
+    type Sample_Type is (ACID, BASE);
+
     PROBLEM_NAME_READABLE: constant String := "Titrační křivka";
+    SAMPLE_TYPE_KEY: constant String := "SAMPLE_TYPE";
+      SAMPLE_TYPE_ACID: constant String := "kyselina";
+      SAMPLE_TYPE_BASE: constant String := "báze";
+    --
+    --
+    SAMPLE_CONC_INT_KEY: constant String := "SAMPLE_CONC_INT";
+    SAMPLE_CONC_DEC_KEY: constant String := "SAMPLE_CONC_DEC";
+    SAMPLE_CONC_EXP_KEY: constant String := "SAMPLE_CONC_EXP";
+    --
+    TITRANT_TYPE_KEY: constant String := "TITRANT_TYPE";
+      TITRANT_TYPE_ACID: constant String := "kyseliny";
+      TITRANT_TYPE_BASE: constant String  := "báze";
+    --
+    TITRANT_CONC_INT_KEY: constant String := "TITRANT_CONC_INT";
+    TITRANT_CONC_DEC_KEY: constant String := "TITRANT_CONC_DEC";
+    TITRANT_CONC_EXP_KEY: constant String := "TITRANT_CONC_EXP";
+    --
+    SAMPLE_VOLUME_INT_KEY: constant String := "SAMPLE_VOLUME_INT";
+    SAMPLE_VOLUME_DEC_KEY: constant String := "SAMPLE_VOLUME_DEC";
+    SAMPLE_VOLUME_EXP_KEY: constant String := "SAMPLE_VOLUME_EXP";
+    --
+    PKX1_INT_KEY: constant String := "PKX1_INT";
+    PKX1_DEC_KEY: constant String := "PKX1_DEC";
+    PKX2_INT_KEY: constant String := "PKX2_INT";
+    PKX2_DEC_KEY: constant String := "PKX2_DEC";
+    --
+    --
+    PH_START_KEY_INT_KEY: constant String := "PH_START_INT";
+    PH_START_KEY_DEC_KEY: constant String := "PH_START_DEC";
+    PH_START_KEY_EXP_KEY: constant String := "PH_START_EXP";
+    --
+    PH_FIRST_HALF_INT_KEY: constant String := "PH_FIRST_HALF_INT";
+    PH_FIRST_HALF_DEC_KEY: constant String := "PH_FIRST_HALF_DEC";
+    PH_FIRST_HALF_EXP_KEY: constant String := "PH_FIRST_HALF_EXP";
+    --
+    PH_FIRST_EQUIV_INT_KEY: constant String := "PH_FIRST_EQUIV_INT";
+    PH_FIRST_EQUIV_DEC_KEY: constant String := "PH_FIRST_EQUIV_DEC";
+    PH_FIRST_EQUIV_EXP_KEY: constant String := "PH_FIRST_EQUIV_EXP";
+    --
+    PH_SECOND_HALF_INT_KEY: constant String := "PH_SECOND_HALF_INT";
+    PH_SECOND_HALF_DEC_KEY: constant String := "PH_SECOND_HALF_DEC";
+    PH_SECOND_HALF_EXP_KEY: constant String := "PH_SECOND_HALF_EXP";
+    --
+    PH_SECOND_EQUIV_INT_KEY: constant String := "PH_SECOND_EQUIV_INT";
+    PH_SECOND_EQUIV_DEC_KEY: constant String := "PH_SECOND_EQUIV_DEC";
+    PH_SECOND_EQUIV_EXP_KEY: constant String := "PH_SECOND_EQUIV_EXP";
+    --
+    PH_OVER_EQUIV_INT_KEY: constant String := "PH_OVER_EQUIV_INT";
+    PH_OVER_EQUIV_DEC_KEY: constant String := "PH_OVER_EQUIV_DEC";
+    PH_OVER_EQUIV_EXP_KEY: constant String := "PH_OVER_EQUIV_EXP";
+    --
+    --
+    ANSWER_PH_START_KEY: constant String := "ANSWER_PH_START";
+    ANSWER_VOLUME_START_KEY: constant String := "ANSWER_VOLUME_START";
+    ANSWER_PH_FIRST_HALF_KEY: constant String := "ANSWER_PH_FIRST_HALF";
+    ANSWER_VOLUME_FIRST_HALF_KEY: constant String := "ANSWER_VOLUME_FIRST_HALF";
+    ANSWER_PH_FIRST_EQUIV_KEY: constant String := "ANSWER_PH_FIRST_EQUIV";
+    ANSWER_VOLUME_FIRST_EQUIV_KEY: constant String := "ANSWER_VOLUME_FIRST_EQUIV";
+    ANSWER_PH_SECOND_HALF_KEY: constant String := "ANSWER_PH_SECOND_HALF";
+    ANSWER_VOLUME_SECOND_HALF_KEY: constant String := "ANSWER_VOLUME_SECOND_HALF";
+    ANSWER_PH_SECOND_EQUIV_KEY: constant String := "ANSWER_PH_SECOND_EQUIV";
+    ANSWER_VOLUME_SECOND_EQUIV_KEY: constant String := "ANSWER_VOLUME_SECOND_EQUIV";
+    ANSWER_PH_OVER_SECOND_EQUIV_KEY: constant String := "ANSWER_PH_OVER_SECOND_EQUIV";
+    ANSWER_VOLUME_OVER_SECOND_EQUIV_KEY: constant String := "ANSWER_VOLUME_OVER_SECOND_EQUIV";
+    --
+    TITRATION_CURVE_IMAGE_PATH_KEY: constant String := "TITRATION_CURVE_IMAGE_PATH";
+
+
   end Titration_Curve_Suite;
 
 end Problem_Generator_Syswides;
index 487825f56b52768506f94b10696dd8125afe70b3..13c09688d30369ecbab3efe5a83666d2dd0685aa 100644 (file)
@@ -1,3 +1,5 @@
+with Ada.Exceptions;
+with Ada.Strings.Fixed;
 with Ada.Text_IO;
 with Ada.Unchecked_Deallocation;
 with Face_Generator;
@@ -18,6 +20,7 @@ package body Problem_Manager is
       return E_NOTFOUND;
     end if;
 
+    Pr_Cat := Stored.Category;
     Ret := Stored.Problem.Get_Parameters(Parameters);
     if Ret /= OK then
       Stored.Mutex.Unlock;
@@ -25,20 +28,18 @@ package body Problem_Manager is
     end if;
 
     begin
-      Ret := Stored.Problem.Get_Assignment(Assignment);
+      Ret := Stored.Problem.Get_Assignment(Assignment, Build_Resource_Prefix(UID, Pr_Cat, Pr_ID));
       if Ret /= OK then
        Stored.Mutex.Unlock;
        return Face_Generator.Generate_Error_Face(HTML, ERRMSG_GET_ASSIGNMENT & " (" & RetCode'Image(Ret) & ")");
       end if;
     exception
-      when others =>
-       return Face_Generator.Generate_Error_Face(HTML, ERRMSG_UNHANDLED_EXCEPTION);
+      when Ex: others =>
        Stored.Mutex.Unlock;
-       return E_UNKW;
+       return Face_Generator.Generate_Error_Face(HTML, ERRMSG_UNHANDLED_EXCEPTION & " (" & Ada.Exceptions.Exception_Information(Ex) & ")");
     end;
 
     ARC := Stored.Problem.Check_Answer(Answer, Answer_Message);
-    Pr_Cat := Stored.Category;
     Stored.Mutex.Unlock;
     return Face_Generator.Generate_Face_With_Answer(Assignment => Assignment, Answer_Message => Answer_Message,
                                                    Answer_Code => ARC, HTML => HTML,
@@ -58,6 +59,7 @@ package body Problem_Manager is
       return E_NOTFOUND;
     end if;
 
+    Pr_Cat := Stored.Category;
     Ret := Stored.Problem.Get_Parameters(Parameters);
     if Ret /= OK then
       Stored.Mutex.Unlock;
@@ -66,18 +68,17 @@ package body Problem_Manager is
 
     -- Get assignment
     begin
-      Ret := Stored.Problem.Get_Assignment(Assignment);
+      Ret := Stored.Problem.Get_Assignment(Assignment, Build_Resource_Prefix(UID, Pr_Cat, Pr_ID));
       if Ret /= OK then
        Stored.Mutex.Unlock;
        return Face_Generator.Generate_Error_Face(HTML, ERRMSG_GET_ASSIGNMENT & " (" & RetCode'Image(Ret) & ")");
       end if;
     exception
-      when others =>
+      when Ex: others =>
        Stored.Mutex.Unlock;
-       return Face_Generator.Generate_Error_Face(HTML, ERRMSG_UNHANDLED_EXCEPTION);
+       return Face_Generator.Generate_Error_Face(HTML, ERRMSG_UNHANDLED_EXCEPTION & " (" & Ada.Exceptions.Exception_Information(Ex) & ")");
     end;
 
-    Pr_Cat := Stored.Category;
     Stored.Mutex.Unlock;
     return Face_Generator.Generate_Face(Assignment => Assignment, HTML => HTML, Parameters => Parameters, Pr_ID => Problem_ID'Image(Pr_ID),
                                        Pr_Cat => Problem_Category'Image(Pr_Cat));
@@ -174,6 +175,11 @@ package body Problem_Manager is
 
   -- BEGIN: Private functions
 
+  function Build_Resource_Prefix(UID: in Unique_ID; Pr_Cat: in Problem_Category; Pr_ID: in Problem_ID) return String is
+  begin
+    return "resources/resource_" & Ada.Strings.Fixed.Trim(Source => Unique_ID'Image(UID), Side => Ada.Strings.Left) & "_" & Ada.Strings.Fixed.Trim(Source => Problem_Category'Image(Pr_Cat), Side => Ada.Strings.Left) & "_" & Ada.Strings.Fixed.Trim(Source => Problem_ID'Image(Pr_ID), Side => Ada.Strings.Left);
+  end Build_Resource_Prefix;
+
   procedure Free_Chem_Problem(Problem: in out Chem_Problem_All_Access) is
     procedure Free_Chem_Problem_Internal is new Ada.Unchecked_Deallocation(Object => Problem_Generator.Chem_Problem'Class, Name => Chem_Problem_All_Access);
   begin
@@ -214,7 +220,7 @@ package body Problem_Manager is
       declare
        Del_ID: Problem_ID;
       begin
-       if USD.Last_Problem_ID - 1 <= MAX_STORED_PROBLEMS then
+       if USD.Last_Problem_ID - 1 < MAX_STORED_PROBLEMS then
          Del_ID := Problem_ID'Last - (MAX_STORED_PROBLEMS - (USD.Last_Problem_ID - 1));
        else
          Del_ID := (USD.Last_Problem_ID - 1) - MAX_STORED_PROBLEMS;
index a0046be60e779fa2fd742dceb888c7e7cee349ee..5437473000d2436ecaeba1197b984dc6318e803c 100644 (file)
@@ -34,6 +34,8 @@ private
   type Stored_Problem_All_Access is access all Stored_Problem;
   procedure Free_Stored_Problem(Problem: in out Stored_Problem_All_Access);
 
+  function Build_Resource_Prefix(UID: in Unique_ID; Pr_Cat: in Problem_Category; Pr_ID: in Problem_ID) return String;
+
   package Problem_Storage is new Ada.Containers.Ordered_Maps(Key_Type => Problem_ID, Element_Type => Stored_Problem_All_Access);
 
   type User_Session_Data is