% phtitration package
% Copyright (c) 2026 Khaldi Mohammed Elhadi
% Released under the MIT License
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{phtitration}[2026/08/16 v1.0.0 Accurate pH titration curves]

\RequirePackage{tikz}
\RequirePackage{pgfplots}
\RequirePackage{xstring}
\RequirePackage{etoolbox}
\usetikzlibrary{calc, decorations.markings}

\pgfplotsset{compat=1.18}

% =========================================================
% Internal variables
% =========================================================

\newcommand{\pht@type}{weak-acid}

\newcommand{\pht@Ca}{0.1}
\newcommand{\pht@Va}{20}
\newcommand{\pht@Cb}{0.1}
\newcommand{\pht@Vb}{20}

\newcommand{\pht@pKa}{4.76}
\newcommand{\pht@pKaone}{4.76}
\newcommand{\pht@pKatwo}{9.24}
\newcommand{\pht@pKathree}{12.0}

\newcommand{\pht@pKb}{4.75}
\newcommand{\pht@pKbone}{4.75}
\newcommand{\pht@pKbtwo}{8.00}
\newcommand{\pht@pKbthree}{11.00}

% Tangent method variables
\newcommand{\pht@tangent@slope}{0.15}
\newcommand{\pht@tangent@distance}{1.5}
\newcommand{\pht@tangent@length}{10}
\newcommand{\pht@tangent@xshift}{-5}

% Styling variables
\newcommand{\pht@curve@color}{black}
\newcommand{\pht@curve@style}{very thick}
\newcommand{\pht@tangent@color}{black}
\newcommand{\pht@tangent@style}{thin}
\newcommand{\pht@eq@color}{black}
\newcommand{\pht@eq@mark}{*}
\newcommand{\pht@eq@size}{2pt}
\newcommand{\pht@eq@label@pos}{above left}
\newcommand{\pht@font@style}{\footnotesize}

\newif\ifpht@equivalence
\newif\ifpht@half
\newif\ifpht@tangents
\newif\ifpht@coordinates

\pht@equivalencetrue
\pht@halftrue
\pht@tangentsfalse
\pht@coordinatesfalse

% =========================================================
% TikZ Styles for Tangent Method (Equality marks //)
% =========================================================
\tikzset{
	pht tick/.style={
		decoration={
			markings,
			mark=at position 0.5 with {
				\draw[solid, thick, \pht@tangent@color] (-2pt,-3pt) -- (1pt,3pt);
				\draw[solid, thick, \pht@tangent@color] (0pt,-3pt) -- (3pt,3pt);
			}
		},
		postaction={decorate}
	}
}

% =========================================================
% Keys
% =========================================================

\pgfkeys{
	/pht/.is family,
	/pht,
	type/.store in=\pht@type,
	Ca/.store in=\pht@Ca,
	Va/.store in=\pht@Va,
	Cb/.store in=\pht@Cb,
	Vb/.store in=\pht@Vb,
	pKa/.store in=\pht@pKa,
	pKa1/.store in=\pht@pKaone,
	pKa2/.store in=\pht@pKatwo,
	pKa3/.store in=\pht@pKathree,
	pKb/.store in=\pht@pKb,
	pKb1/.store in=\pht@pKbone,
	pKb2/.store in=\pht@pKbtwo,
	pKb3/.store in=\pht@pKbthree,
	show-equivalence/.is if=pht@equivalence,
	show-equivalence=true,
	show-half-equivalence/.is if=pht@half,
	show-half-equivalence=false,
	show-tangents/.is if=pht@tangents,
	show-tangents=false,
	show-coordinates/.is if=pht@coordinates,
	show-coordinates=false,
	tangent-slope/.store in=\pht@tangent@slope,
	tangent-distance/.store in=\pht@tangent@distance,
	tangent-length/.store in=\pht@tangent@length,
	tangent-xshift/.store in=\pht@tangent@xshift,
	curve-color/.store in=\pht@curve@color,
	curve-style/.store in=\pht@curve@style,
	tangent-color/.store in=\pht@tangent@color,
	tangent-style/.store in=\pht@tangent@style,
	eq-color/.store in=\pht@eq@color,
	eq-mark/.store in=\pht@eq@mark,
	eq-size/.store in=\pht@eq@size,
	eq-label-pos/.store in=\pht@eq@label@pos,
	font-style/.store in=\pht@font@style,
}

% =========================================================
% Helper Macro: Draw Coordinates Text
% =========================================================
\newcommand{\pht@label}[3][\pht@eq@label@pos]{%
	\ifpht@coordinates
	\node[#1, font=\pht@font@style, text=\pht@eq@color] at ({#2},{#3}) {$({\pgfmathprintnumber[fixed, precision=2]{#2}}, {\pgfmathprintnumber[fixed, precision=2]{#3}})$};
	\fi
}

% =========================================================
% Helper Macro: Draw Tangent Method
% =========================================================
\newcommand{\pht@draw@tangents}[3]{%
	\ifpht@tangents
	\pgfmathsetmacro{\m}{\pht@tangent@slope * #3}
	\pgfmathsetmacro{\dx}{\pht@tangent@length}
	\pgfmathsetmacro{\dy}{\pht@tangent@distance}
	\pgfmathsetmacro{\xshift}{\pht@tangent@xshift}
	
	\coordinate (T1A) at ({#1 - \dx}, {#2 - \dy - \m*\dx});
	\coordinate (T1B) at ({#1 + \dx}, {#2 - \dy + \m*\dx});
	
	\coordinate (T2A) at ({#1 - \dx}, {#2 + \dy - \m*\dx});
	\coordinate (T2B) at ({#1 + \dx}, {#2 + \dy + \m*\dx});
	
	\coordinate (TMA) at ({#1 - \dx}, {#2 - \m*\dx});
	\coordinate (TMB) at ({#1 + \dx}, {#2 + \m*\dx});
	
	\draw[\pht@tangent@style, \pht@tangent@color] (T1A) -- (T1B);
	\draw[\pht@tangent@style, \pht@tangent@color] (T2A) -- (T2B);
	\draw[\pht@tangent@style, \pht@tangent@color, dashed] (TMA) -- (TMB);
	
	\coordinate (P1) at ({#1 + \xshift}, {#2 - \dy + \m*\xshift});
	\coordinate (PM) at ($(TMA)!(P1)!(TMB)$);
	\coordinate (P2) at ($(T2A)!(P1)!(T2B)$);
	
	\draw[\pht@tangent@style, \pht@tangent@color] (P1) -- (PM) [pht tick];
	\draw[\pht@tangent@style, \pht@tangent@color] (PM) -- (P2) [pht tick];
	
	\draw[dashed, \pht@tangent@style, gray] ({#1}, 0) node[below, font=\pht@font@style, text=\pht@eq@color] {$V_E$} -- ({#1}, {#2}) node[\pht@eq@label@pos, inner sep=2pt, font=\pht@font@style, text=\pht@eq@color] {$E$} -- (0, {#2}) node[left, font=\pht@font@style, text=\pht@eq@color] {$\text{pH}_E$};
	\fi
}

% =========================================================
% Main command
% =========================================================

\newcommand{\phtitration}[1][]{%
	\pgfkeys{/pht,#1}%
	
	% -----------------------------------------------------
	% Strong acid + strong base
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{strong-acid}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Ca*\pht@Va/\pht@Cb }
		\addplot[\pht@curve@style, \pht@curve@color, samples=300, domain=0:\pht@Ve*2]
		({x}, { x < \pht@Ve ? -log10(max(1e-14, (\pht@Ca*\pht@Va-\pht@Cb*x)/(\pht@Va+x))) : 14+log10(max(1e-14, (\pht@Cb*x-\pht@Ca*\pht@Va)/(\pht@Va+x))) });
		
		\ifpht@equivalence
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {({\pht@Ve},{7})};
		\pht@label{\pht@Ve}{7}
		\fi
		\pht@draw@tangents{\pht@Ve}{7}{1}
	}{}
	
	% -----------------------------------------------------
	% Weak monoprotic acid + strong base
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{weak-acid}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Ca*\pht@Va/\pht@Cb }
		\addplot[\pht@curve@style, \pht@curve@color, samples=400, domain=0:\pht@Ve*2]
		({x}, { x < \pht@Ve ? ( x == 0 ? -log10(sqrt(10^(-\pht@pKa)*\pht@Ca)) : \pht@pKa+log10(max(1e-14, \pht@Cb*x/max(1e-14, \pht@Ca*\pht@Va-\pht@Cb*x))) ) : 14+log10(max(1e-14, (\pht@Cb*x-\pht@Ca*\pht@Va)/(\pht@Va+x))) });
		
		\ifpht@half
		\pgfmathsetmacro{\pht@Vhalf}{\pht@Ve/2}
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {({\pht@Vhalf},{\pht@pKa})};
		\pht@label[below right]{\pht@Vhalf}{\pht@pKa}
		\ifpht@tangents
		\draw[dashed, \pht@tangent@style, gray] ({\pht@Vhalf}, 0) node[below, font=\pht@font@style, text=\pht@eq@color] {$\frac{V_E}{2}$} -- ({\pht@Vhalf}, {\pht@pKa}) -- (0, {\pht@pKa}) node[left, font=\pht@font@style, text=\pht@eq@color] {$\text{pH} = pK_a$};
		\fi
		\fi
		
		\pgfmathsetmacro{\pht@Csalt}{\pht@Ca*\pht@Va/(\pht@Va+\pht@Ve)}
		\pgfmathsetmacro{\pht@pHeq}{7 + 0.5*\pht@pKa + 0.5*log10(\pht@Csalt)}
		
		\ifpht@equivalence
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {({\pht@Ve},{\pht@pHeq})};
		\pht@label{\pht@Ve}{\pht@pHeq}
		\fi
		\pht@draw@tangents{\pht@Ve}{\pht@pHeq}{1}
	}{}
	
	% -----------------------------------------------------
	% Diprotic acid
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{diprotic-acid}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Ca*\pht@Va/\pht@Cb }
		\pgfmathsetmacro{\pht@VeTwo}{ 2*\pht@Ve }
		\pgfmathsetmacro{\pht@MaxVol}{\pht@VeTwo * 1.5}
		
		\addplot[\pht@curve@style, \pht@curve@color, samples=500, domain=0:14, variable=\ph, restrict x to domain=0:\pht@MaxVol]
		( { \pht@Va * ( \pht@Ca * ( 10^(-\ph - \pht@pKaone) + 2 * 10^(-\pht@pKaone - \pht@pKatwo) ) / ( 10^(-2*\ph) + 10^(-\ph - \pht@pKaone) + 10^(-\pht@pKaone - \pht@pKatwo) ) - ( 10^(-\ph) - 10^(\ph - 14) ) ) / ( \pht@Cb + ( 10^(-\ph) - 10^(\ph - 14) ) ) }, { \ph } );
		
		\ifpht@equivalence
		\pgfmathsetmacro{\pht@CsaltTwo}{\pht@Ca*\pht@Va/(\pht@Va+\pht@VeTwo)}
		\pgfmathsetmacro{\pht@pHeqTwo}{7 + 0.5*\pht@pKatwo + 0.5*log10(\pht@CsaltTwo)}
		\pgfmathsetmacro{\pht@pHone}{(\pht@pKaone+\pht@pKatwo)/2}
		
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {
			({\pht@Ve},{\pht@pHone})
			({\pht@VeTwo},{\pht@pHeqTwo}) 
		};
		\pht@label{\pht@Ve}{\pht@pHone}
		\pht@label{\pht@VeTwo}{\pht@pHeqTwo}
		\fi
		\pht@draw@tangents{\pht@VeTwo}{\pht@pHeqTwo}{1}
	}{}
	
	% -----------------------------------------------------
	% Triprotic acid
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{triprotic-acid}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Ca*\pht@Va/\pht@Cb }
		\pgfmathsetmacro{\pht@VeTwo}{ 2*\pht@Ve }
		\pgfmathsetmacro{\pht@VeThree}{ 3*\pht@Ve }
		\pgfmathsetmacro{\pht@MaxVol}{\pht@VeThree * 1.3}
		
		\addplot[\pht@curve@style, \pht@curve@color, samples=600, domain=0:14, variable=\ph, restrict x to domain=0:\pht@MaxVol]
		( { \pht@Va * ( \pht@Ca * ( 10^(-2*\ph - \pht@pKaone) + 2 * 10^(-\ph - \pht@pKaone - \pht@pKatwo) + 3 * 10^(-\pht@pKaone - \pht@pKatwo - \pht@pKathree) ) / ( 10^(-3*\ph) + 10^(-2*\ph - \pht@pKaone) + 10^(-\ph - \pht@pKaone - \pht@pKatwo) + 10^(-\pht@pKaone - \pht@pKatwo - \pht@pKathree) ) - ( 10^(-\ph) - 10^(\ph - 14) ) ) / ( \pht@Cb + ( 10^(-\ph) - 10^(\ph - 14) ) ) }, { \ph } );
		
		\ifpht@equivalence
		\pgfmathsetmacro{\pht@CsaltThree}{\pht@Ca*\pht@Va/(\pht@Va+\pht@VeThree)}
		\pgfmathsetmacro{\pht@pHeqThree}{7 + 0.5*\pht@pKathree + 0.5*log10(\pht@CsaltThree)}
		\pgfmathsetmacro{\pht@pHone}{(\pht@pKaone+\pht@pKatwo)/2}
		\pgfmathsetmacro{\pht@pHtwo}{(\pht@pKatwo+\pht@pKathree)/2}
		
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {
			({\pht@Ve},{\pht@pHone})
			({\pht@VeTwo},{\pht@pHtwo})
			({\pht@VeThree},{\pht@pHeqThree})
		};
		\pht@label{\pht@Ve}{\pht@pHone}
		\pht@label{\pht@VeTwo}{\pht@pHtwo}
		\pht@label{\pht@VeThree}{\pht@pHeqThree}
		\fi
		\pht@draw@tangents{\pht@VeThree}{\pht@pHeqThree}{1}
	}{}
	
	% -----------------------------------------------------
	% Strong base + strong acid
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{strong-base}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Cb*\pht@Vb/\pht@Ca }
		\addplot[\pht@curve@style, \pht@curve@color, samples=300, domain=0:\pht@Ve*2]
		({x}, { x < \pht@Ve ? 14+log10(max(1e-14, (\pht@Cb*\pht@Vb-\pht@Ca*x)/(\pht@Vb+x))) : -log10(max(1e-14, (\pht@Ca*x-\pht@Cb*\pht@Vb)/(\pht@Vb+x))) });
		
		\ifpht@equivalence
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {({\pht@Ve},{7})};
		\pht@label{\pht@Ve}{7}
		\fi
		\pht@draw@tangents{\pht@Ve}{7}{-1} 
	}{}
	
	% -----------------------------------------------------
	% Weak base + strong acid
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{weak-base}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Cb*\pht@Vb/\pht@Ca }
		\addplot[\pht@curve@style, \pht@curve@color, samples=400, domain=0:\pht@Ve*2]
		({x}, { x < \pht@Ve ? 14-( \pht@pKb+log10(max(1e-14, \pht@Ca*x/max(1e-14, \pht@Cb*\pht@Vb-\pht@Ca*x))) ) : -log10(max(1e-14, (\pht@Ca*x-\pht@Cb*\pht@Vb)/(\pht@Vb+x))) });
		
		\ifpht@half
		\pgfmathsetmacro{\pht@Vhalf}{\pht@Ve/2}
		\pgfmathsetmacro{\pht@pHhalf}{14-\pht@pKb}
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {({\pht@Vhalf},{\pht@pHhalf})};
		\pht@label[above right]{\pht@Vhalf}{\pht@pHhalf}
		\ifpht@tangents
		\draw[dashed, \pht@tangent@style, gray] ({\pht@Vhalf}, 0) node[below, font=\pht@font@style, text=\pht@eq@color] {$\frac{V_E}{2}$} -- ({\pht@Vhalf}, {\pht@pHhalf}) -- (0, {\pht@pHhalf}) node[left, font=\pht@font@style, text=\pht@eq@color] {$\text{pH} = 14-pK_b$};
		\fi
		\fi
		
		\pgfmathsetmacro{\pht@Csalt}{\pht@Cb*\pht@Vb/(\pht@Vb+\pht@Ve)}
		\pgfmathsetmacro{\pht@pHeq}{7 - 0.5*\pht@pKb - 0.5*log10(\pht@Csalt)}
		
		\ifpht@equivalence
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {({\pht@Ve},{\pht@pHeq})};
		\pht@label{\pht@Ve}{\pht@pHeq}
		\fi
		\pht@draw@tangents{\pht@Ve}{\pht@pHeq}{-1}
	}{}
	
	% -----------------------------------------------------
	% Dibase
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{dibase}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Cb*\pht@Vb/\pht@Ca }
		\pgfmathsetmacro{\pht@VeTwo}{ 2*\pht@Ve }
		\pgfmathsetmacro{\pht@MaxVol}{\pht@VeTwo * 1.5}
		
		\addplot[\pht@curve@style, \pht@curve@color, samples=500, domain=0:14, variable=\ph, restrict x to domain=0:\pht@MaxVol]
		( { \pht@Vb * ( \pht@Cb * ( 10^(\ph - 14 - \pht@pKbone) + 2 * 10^(-\pht@pKbone - \pht@pKbtwo) ) / ( 10^(2*\ph - 28) + 10^(\ph - 14 - \pht@pKbone) + 10^(-\pht@pKbone - \pht@pKbtwo) ) + ( 10^(-\ph) - 10^(\ph - 14) ) ) / ( \pht@Ca - ( 10^(-\ph) - 10^(\ph - 14) ) ) }, { \ph } );
		
		\ifpht@equivalence
		\pgfmathsetmacro{\pht@CsaltTwo}{\pht@Cb*\pht@Vb/(\pht@Vb+\pht@VeTwo)}
		\pgfmathsetmacro{\pht@pHeqTwo}{7 - 0.5*\pht@pKbtwo - 0.5*log10(\pht@CsaltTwo)}
		\pgfmathsetmacro{\pht@pHone}{14-(\pht@pKbone+\pht@pKbtwo)/2}
		
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {
			({\pht@Ve},{\pht@pHone})
			({\pht@VeTwo},{\pht@pHeqTwo}) 
		};
		\pht@label{\pht@Ve}{\pht@pHone}
		\pht@label{\pht@VeTwo}{\pht@pHeqTwo}
		\fi
		\pht@draw@tangents{\pht@VeTwo}{\pht@pHeqTwo}{-1}
	}{}
	
	% -----------------------------------------------------
	% Tribase
	% -----------------------------------------------------
	\ifdefstring{\pht@type}{tribase}{%
		\pgfmathsetmacro{\pht@Ve}{ \pht@Cb*\pht@Vb/\pht@Ca }
		\pgfmathsetmacro{\pht@VeTwo}{ 2*\pht@Ve }
		\pgfmathsetmacro{\pht@VeThree}{ 3*\pht@Ve }
		\pgfmathsetmacro{\pht@MaxVol}{\pht@VeThree * 1.3}
		
		\addplot[\pht@curve@style, \pht@curve@color, samples=600, domain=0:14, variable=\ph, restrict x to domain=0:\pht@MaxVol]
		( { \pht@Vb * ( \pht@Cb * ( 10^(2*\ph - 28 - \pht@pKbone) + 2 * 10^(\ph - 14 - \pht@pKbone - \pht@pKbtwo) + 3 * 10^(-\pht@pKbone - \pht@pKbtwo - \pht@pKbthree) ) / ( 10^(3*\ph - 42) + 10^(2*\ph - 28 - \pht@pKbone) + 10^(\ph - 14 - \pht@pKbone - \pht@pKbtwo) + 10^(-\pht@pKbone - \pht@pKbtwo - \pht@pKbthree) ) + ( 10^(-\ph) - 10^(\ph - 14) ) ) / ( \pht@Ca - ( 10^(-\ph) - 10^(\ph - 14) ) ) }, { \ph } );
		
		\ifpht@equivalence
		\pgfmathsetmacro{\pht@CsaltThree}{\pht@Cb*\pht@Vb/(\pht@Vb+\pht@VeThree)}
		\pgfmathsetmacro{\pht@pHeqThree}{7 - 0.5*\pht@pKbthree - 0.5*log10(\pht@CsaltThree)}
		\pgfmathsetmacro{\pht@pHone}{14-(\pht@pKbone+\pht@pKbtwo)/2}
		\pgfmathsetmacro{\pht@pHtwo}{14-(\pht@pKbtwo+\pht@pKbthree)/2}
		
		\addplot[only marks, mark=\pht@eq@mark, mark size=\pht@eq@size, \pht@eq@color] coordinates {
			({\pht@Ve},{\pht@pHone})
			({\pht@VeTwo},{\pht@pHtwo})
			({\pht@VeThree},{\pht@pHeqThree}) 
		};
		\pht@label{\pht@Ve}{\pht@pHone}
		\pht@label{\pht@VeTwo}{\pht@pHtwo}
		\pht@label{\pht@VeThree}{\pht@pHeqThree}
		\fi
		\pht@draw@tangents{\pht@VeThree}{\pht@pHeqThree}{-1}
	}{}
}

\endinput