From f59c0783b226cacf4bd37c4ec09baf2b9863e54c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20Mal=C3=BD?= Date: Mon, 15 Dec 2014 23:29:14 +0100 Subject: [PATCH] First implementation of "Titration Curve" suite plus necessary infrastructure improvements --- bin/resources/noimage.png | Bin 0 -> 169 bytes bin/templates/face_index.html | 5 +- bin/templates/face_titration_curve.html | 89 +++ .../titration_curve_answer_section.html | 4 + nine_q.gpr | 1 + src/face_generators/face_generator.adb | 88 +++ src/face_generators/face_generator.ads | 7 + src/handlers/handler_resources.ads | 5 + src/handlers/handlers.adb | 4 + .../problem_generator-acidobazic_suite.adb | 2 +- .../problem_generator-solubility_suite.adb | 2 +- ...roblem_generator-titration_curve_suite.adb | 659 +++++++++++++++++- src/problem_generators/problem_generator.ads | 88 ++- .../problem_generator_syswides.ads | 71 ++ src/problem_manager.adb | 26 +- src/problem_manager.ads | 2 + 16 files changed, 1025 insertions(+), 28 deletions(-) create mode 100644 bin/resources/noimage.png create mode 100644 bin/templates/face_titration_curve.html create mode 100644 bin/templates/titration_curve_answer_section.html create mode 100644 src/handlers/handler_resources.ads diff --git a/bin/resources/noimage.png b/bin/resources/noimage.png new file mode 100644 index 0000000000000000000000000000000000000000..cae466e2e0053c2a8d2109a4e6ed9b90e59c34d8 GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&kwQhhL0CWh&F`%|Id9%#er@=ltB<)VvZPmw~~#C^fMp zHASI3vm`^o-P1Q9MK6^dD9-EY;uyklJvjjgfLvAv#<mdKI;Vst0NX?+ A%m4rY literal 0 HcmV?d00001 diff --git a/bin/templates/face_index.html b/bin/templates/face_index.html index f1320be..47ef538 100644 --- a/bin/templates/face_index.html +++ b/bin/templates/face_index.html @@ -1,4 +1,5 @@ - pH jednosytné kyseliny/báze - Srážecí rovnováhy + pH jednosytné kyseliny/báze
+ Srážecí rovnováhy
+ Titrační křivka
diff --git a/bin/templates/face_titration_curve.html b/bin/templates/face_titration_curve.html new file mode 100644 index 0000000..348ec5b --- /dev/null +++ b/bin/templates/face_titration_curve.html @@ -0,0 +1,89 @@ + +
+
Zadání:
+
@_SAMPLE_VOLUME_INT_@,@_SAMPLE_VOLUME_DEC_@ . 10@_SAMPLE_VOLUME_EXP_@ dm3 dvojsytné @_SAMPLE_TYPE_@ o koncentraci @_SAMPLE_CONC_INT_@,@_SAMPLE_CONC_DEC_@ . 10@_SAMPLE_CONC_EXP_@ mol/L o pKx1 = @_PKX1_INT_@,@_PKX1_DEC_@ a pKx2 = @_PKX2_INT_@,@_PKX2_DEC_@ je titrováno odměrným roztokem jednosytné @_TITRANT_TYPE_@ o koncentraci @_TITRANT_CONC_INT_@,@_TITRANT_CONC_DEC_@ . 10@_TITRANT_CONC_EXP_@ mol/L. Spočítejte pH roztoku a objem přidaného titračního činidla v těchto bodech: +
+
    +
  • Na počátku titrace
  • +
  • V polovině první ekvivalence
  • +
  • V první ekvivalenci
  • +
  • V polovině druhé ekvivalence
  • +
  • V druhé ekvivalenci
  • +
  • V 1,5násobku druhé ekvivalence
  • +
+
+
+ +
+ Počátek: + + +
+ +
+ Polovina první ekvivalence: + + + + +
+ +
+ První ekvivalence: + + + + +
+ +
+ Polovina druhé ekvivalence: + + + + +
+ +
+ Druhá ekvivalence: + + + + +
+ +
+ Nadbytek: + + + + +
+ +
+ +
+
+
+
+ + @_ANSWER_SECTION_@ + +
+ + Zobraz sekci + +
Nápověda:
+
+ @_HINTS_SECTION_@ +
+
+ +
+
+ +
+ +
+
+
diff --git a/bin/templates/titration_curve_answer_section.html b/bin/templates/titration_curve_answer_section.html new file mode 100644 index 0000000..bfde9f7 --- /dev/null +++ b/bin/templates/titration_curve_answer_section.html @@ -0,0 +1,4 @@ +
+
@_ANSWER_MESSAGE_@
+ +
diff --git a/nine_q.gpr b/nine_q.gpr index ece67dc..4906896 100644 --- a/nine_q.gpr +++ b/nine_q.gpr @@ -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 diff --git a/src/face_generators/face_generator.adb b/src/face_generators/face_generator.adb index 41f47c8..cac0ea3 100644 --- a/src/face_generators/face_generator.adb +++ b/src/face_generators/face_generator.adb @@ -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, "< " & 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; diff --git a/src/face_generators/face_generator.ads b/src/face_generators/face_generator.ads index 5e546d0..ba51388 100644 --- a/src/face_generators/face_generator.ads +++ b/src/face_generators/face_generator.ads @@ -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 index 0000000..1a45247 --- /dev/null +++ b/src/handlers/handler_resources.ads @@ -0,0 +1,5 @@ +with AWS.Dispatchers.Callback; + +package Handler_Resources is + function Callback return AWS.Dispatchers.Callback.Handler; +end Handler_Resources; diff --git a/src/handlers/handlers.adb b/src/handlers/handlers.adb index 3bf75c0..2af5b58 100644 --- a/src/handlers/handlers.adb +++ b/src/handlers/handlers.adb @@ -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; diff --git a/src/problem_generators/problem_generator-acidobazic_suite.adb b/src/problem_generators/problem_generator-acidobazic_suite.adb index cde1cd6..12ef83a 100644 --- a/src/problem_generators/problem_generator-acidobazic_suite.adb +++ b/src/problem_generators/problem_generator-acidobazic_suite.adb @@ -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; diff --git a/src/problem_generators/problem_generator-solubility_suite.adb b/src/problem_generators/problem_generator-solubility_suite.adb index d2876da..fad5287 100644 --- a/src/problem_generators/problem_generator-solubility_suite.adb +++ b/src/problem_generators/problem_generator-solubility_suite.adb @@ -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; diff --git a/src/problem_generators/problem_generator-titration_curve_suite.adb b/src/problem_generators/problem_generator-titration_curve_suite.adb index c54fec3..835a019 100644 --- a/src/problem_generators/problem_generator-titration_curve_suite.adb +++ b/src/problem_generators/problem_generator-titration_curve_suite.adb @@ -1,37 +1,682 @@ +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); + + <> + 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; diff --git a/src/problem_generators/problem_generator.ads b/src/problem_generators/problem_generator.ads index ab646d2..3ad5e7d 100644 --- a/src/problem_generators/problem_generator.ads +++ b/src/problem_generators/problem_generator.ads @@ -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; diff --git a/src/problem_generators/problem_generator_syswides.ads b/src/problem_generators/problem_generator_syswides.ads index b6b238e..9caa4ad 100644 --- a/src/problem_generators/problem_generator_syswides.ads +++ b/src/problem_generators/problem_generator_syswides.ads @@ -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; diff --git a/src/problem_manager.adb b/src/problem_manager.adb index 487825f..13c0968 100644 --- a/src/problem_manager.adb +++ b/src/problem_manager.adb @@ -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; diff --git a/src/problem_manager.ads b/src/problem_manager.ads index a0046be..5437473 100644 --- a/src/problem_manager.ads +++ b/src/problem_manager.ads @@ -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 -- 2.43.5