6
\$\begingroup\$

Function

Gives the mass needed of specific basic (pH above 7) substance in order to neutralise the pH of a specific acidic substance.

from decimal import Decimal as d

# m = mass
# n = moles
# M = molar mass

MkHCO3 = 100.11
MnaKHCO3 = 84.007
MphenHCl = 215.67

moles = lambda m,M: m/M

nPhenHCl = moles(1.54, MphenHCl)

mSubstance = lambda x,y: x*y

mKHCO3 = mSubstance(MkHCO3, nPhenHCl) 
mNaHCO3 = mSubstance(MnaKHCO3, nPhenHCl)

nl = '\n'

print(f'{nl}KHCO3:')
print(f'{d(mKHCO3):.2f}g{nl}')

print(f'NaHCO3:')
print(f'{d(mNaHCO3):.2f}g{nl}')

Output


KHCO3:
0.71g

NaHCO3:
0.60g

Background

I know using a named lambda defeats the purpose but the function was so small I couldn't resist.

This script is just for me which is why I hardcoded the value of the nootropic. I use single variables because I'm a chemist and that's how these equations are. If it was for others I wouldn't do these things.

I usually write bash scripts to do things on the filesystem, I could do that with python but there's too much overhead, bash is better suited for that task.

In doing so I write scripts (like the above) with a single use in mind.

Which is why classes are hard for me to grasp.

I've not yet come up with an idea big enough to use a class I think that's my problem, also because I don't practice much and online example are silly and don't apply to the real world.

The above code is probably useless to use as a class, is that right?

I need ideas on when a class would be required or how you'd even turn the above code into a class or if it's just a waste of time.


Class Idea, too much?

If I compiled a bunch of acid bases and made a class for that? if you have a weak acid (vinegar) plus a strong base (sodium hydroxide) then you have to use logs and more complex equations, for a weak acid + weak base, (or strong acid + weak base and vice versa) you also have rate equations which will tell you how long these reactions will take.

I could also add reaction feasibility stuff which will tell you if a reaction is possible. Other things like deriving the pressure and temp for a reaction to occur. Could I have a class for that or is it too much?

For the curious because of the shambles that went on in the comments (now unavailable to see)

All this info is really not needed that's why I said 'for the curious'.

Neutralisation

NaHCO3(aq) + phen-HCl(aq) -> phen(aq) + NaCl(aq) + H2O(l) + CO2(g)

aq means aqueous (something dissolved in water)

Aqueous bicarbonate salt + aqueous medicine salt -> aqueous medicine + water + gaseous carbon dioxide

From the equation you can see that the reactants are 1:1.

Simpler Neutralisation

HCl(aq) + NaOH(aq) -> NaCl(aq) + H2O(l)

You might notice most medicines are salts; med-hcl, med-phosphate etc, this is for long shelf life or because med might smell really bad besides other reasons.

Moles (n) is just a ratio, The moles function, gives a decimal which you can multiply against the other compounds molar mass in order to find out how much equal mass it needs to react completely to make the products.

Why you have to multiply n by molar mass

Molar mass differs for each atoms/compounds (or say particles to group them all) this is because their weight varies; they have less or more protons, neutrons and electrons.

Molar mass has the same number of particles for any atom/molecule/ compound per mol.

molarMass = mass which has 6.023*10^23 particles.

This is Avogadro's number or NA and is what 1 mol means, mol is different from moles.

1 mol = mass which contains NA particles.
1 moles = mass/molarMass = ratio

So you need to multiply n by NA to ensure equal numbers of particles react.

Demystifying moles (n)

Edited, I was sleepy and had nonsense before.

x = £20 per 100g

y = £12 per 50g

z = what the price of y should be.

Where x and y are the same products.

You want to know the price per 1g. So you can know z.


x[ratio] = 1 / 100 = 0.01

x[price per 1g] = x[ratio] * x[price] = £0.20 per 1g

y[price per 1g] = x[ratio] * y[price] = £0.12 per 1g

z[price per 50g] = y[price] * 50 = £6.00 per 50g

That's all moles is and you probably do it to compare small vs large products prices to see which is cheaper/how much you save.

Ionic and covalent bonding

KHCO3 is an inorganic compound. K+ binds to bicarbonate or HCO3- creating a salt; potassium bicarbonate, this is an ionic bond (two opposite charges attracting). Salts have ionic bonds and break in water (excluding some crystals) giving ions; atoms/molecules with charges, this is why salt water is more conductive than water.

More on ions

Water exists in an equilibrium:

H2O <-> H3O+
~1      ~0

When electricity is passed through water, it ionises much more greatly, causing the position of equilibrium to greatly shift to the right.

The bonding in KHCO3: K(+)(-O3-C-H)

C is bonded covalently (the dashes -) to each of the oxygen atoms and hydrogen atom, this bond is strong, electrons are shared, and they are bound by the strong nuclear force and require high energy to ionise, they remain unchanged in water.

This is the way salts work, and why they ionise in water. Take NaCl it's Na(+)Cl(-). You add water and it ionises to Na(+) + Cl(-). The free Na(+) is what allows you to taste salt.

H2O2 represents Hydrogen Peroxide. When used as a variable name it doesn't have the same meaning as in chemistry. It is just a string and the underlying object is an integer. It doesn't have chemical properties.

So the variables contain the molar mass of the compound. The only physical property I'm interested in is its molar mass which is an integer.

You can calculate the mass for a reaction very easily, chemical properties have nothing to do with the calculation, only physical properties do like mass in this case.

Example of 2:1 reaction

H2O2(aq) -> 2H2O(l) + O2(g)

If this is reversed, take the molar mass of both of the reactants then say for O2 you have has a mass of 10g:

O2[n] = O2[mass] / O2[molarMass]
 
H2O[mass required to completely react with O2] = O2[n] * (H2O[molarMass] * 2)

That was all done without chemical properties, only physical properties. Might be obvious but to the fervent few well I hope this calms you, for the curious I hope you learned something.

Conclusion

I used to add about half a teaspoon of baking soda or potassium bicarbonate, this gave a salty or bitter taste respectively and was irritating to the throat.

Using the calculated masses for the bases there is hardly a taste and no irritation.

\$\endgroup\$
6
  • \$\begingroup\$ If you have more complex code then maybe we can demonstrate good use of a class, but as-is a class would be difficult to justify. \$\endgroup\$
    – Reinderien
    Jun 26, 2023 at 15:49
  • \$\begingroup\$ You're doing unit conversion of some kind. Maybe wrap this in a simple command line interface instead of hard coding the inputs and add other conversions that are useful to you. \$\endgroup\$
    – Reinderien
    Jun 26, 2023 at 16:27
  • 1
    \$\begingroup\$ It's certainly not too simple to be reviewed; it's only too simple to justify OOP. As for the tuple suggestion... That's dubious. Domain-specific requirements are real, and I can't pretend to appreciate the chemistry factors at play. \$\endgroup\$
    – Reinderien
    Jun 26, 2023 at 16:33
  • \$\begingroup\$ @Reinderien the reaction is 1:1 moles is a ratio, the moles function, then gives a decimal which you can multiply against the other compound in order to find out how much equal mass it needs to react completely with that compund. \$\endgroup\$
    – Nickotine
    Jun 26, 2023 at 18:16
  • \$\begingroup\$ @Reinderien what if I compiled a bunch of acid bases and made a class for that? if you have a weak acid (vinegar) plus strong base (sodium hydroxide) then you have to use logs and more complex equations, for a weak acid weak base, you also have rate equations which will tell you how long these reactions will take. I could also add reaction feasibility stuff which will tell you if a reaction is possible. Other things like deriving the pressure and temp for a reaction to occur. could I have a class for that or is it too much. \$\endgroup\$
    – Nickotine
    Jun 26, 2023 at 21:03

2 Answers 2

9
\$\begingroup\$

I'll demonstrate two directions for your code. The first is a simplification and output addition - since the code is already so short, I believe that it could be made more legible with units baked into the variable names, so that you can follow along with unit arithmetic just by reading the names nearly as you would on paper. This changes the following:

  • Remove decimal; you don't need it here.
  • Add friendly names for compounds in comments.
  • Consider using the Unicode point for subscript-2 and 3 in your output.
  • Consider using milligrams instead, since they can show more accuracy with fewer characters in this case.
  • Since clarity is of paramount importance in pharmacology, write out your two reactions in full.
  • Don't bother making a constant for a newline.
  • SI convention is to have a space between the quantity and the unit.

More broadly: you're usually writing code on your own (which isn't the end of the world). Seeking a review is a great idea. Adding clear comments, variable names and output content is an important step to branching out so that you aren't the only person who understands your code. Having most of the context for your calculations in your head is fine until it isn't: when their complexity increases, when time passes and (like we all do) things are forgotten, or when you want to share this with someone else.

Units without functions

# Units
g = 1
mg = 1e3 * g
mol = 1
mmol = 1e3 * mol

# Molar masses
KHCO3_g_mol = 100.115   # Potassium bicarbonate
NaHCO3_g_mol = 84.0066  # Sodium bicarbonate
phenHCl_g_mol = 215.67  # Phenibut hydrochloride

# Quantities for neutralization reactions
phenHCl_g = 1.54
phenHCl_mol = phenHCl_g / phenHCl_g_mol
KHCO3_mol = phenHCl_mol
NaHCO3_mol = phenHCl_mol
KHCO3_g = KHCO3_g_mol * KHCO3_mol
NaHCO3_g = NaHCO3_g_mol * NaHCO3_mol

print(f' KHCO₃ + Phen-HCl → Phen + KCl + H₂O + CO₂')
print(f'{KHCO3_mol * mmol/mol:6.3f} + {phenHCl_mol * mmol/mol:8.3f} (mmol)')
print(f'{KHCO3_g * mg/g:6.1f} + {phenHCl_g * mg/g:8.0f} (mg)')
print()

print(f'NaHCO₃ + Phen-HCl → Phen + NaCl + H₂O + CO₂')
print(f'{NaHCO3_mol * mmol/mol:6.3f} + {phenHCl_mol * mmol/mol:8.3f} (mmol)')
print(f'{NaHCO3_g * mg/g:6.1f} + {phenHCl_g * mg/g:8.0f} (mg)')
 KHCO₃ + Phen-HCl → Phen + KCl + H₂O + CO₂
 7.141 +    7.141 (mmol)
 714.9 +     1540 (mg)

NaHCO₃ + Phen-HCl → Phen + NaCl + H₂O + CO₂
 7.141 +    7.141 (mmol)
 599.9 +     1540 (mg)

Units with functions

# Units
g = 1
mg = 1e3 * g
mol = 1
mmol = 1e3 * mol

# Molar masses
KHCO3_g_mol = 100.115   # Potassium bicarbonate
NaHCO3_g_mol = 84.0066  # Sodium bicarbonate
phenHCl_g_mol = 215.67  # Phenibut hydrochloride

# Quantities for neutralization reactions
phenHCl_g = 1.54
phenHCl_mol = phenHCl_g / phenHCl_g_mol


def show_neutralization(salt_name: str, salt_g_mol: float, chloride_name: str) -> None:
    salt_mol = phenHCl_mol
    salt_g = salt_mol * salt_g_mol

    print(f'{salt_name:>6} + Phen-HCl → Phen + {chloride_name} + H₂O + CO₂')
    print(f'{salt_mol * mmol/mol:6.3f} + {phenHCl_mol * mmol/mol:8.3f} (mmol)')
    print(f'{salt_g * mg/g:6.1f} + {phenHCl_g * mg/g:8.0f} (mg)')
    print()


def main() -> None:
    show_neutralization(salt_name='KHCO₃', salt_g_mol=KHCO3_g_mol, chloride_name='KCl')
    show_neutralization(salt_name='NaHCO₃', salt_g_mol=NaHCO3_g_mol, chloride_name='NaCl')


if __name__ == '__main__':
    main()
 KHCO₃ + Phen-HCl → Phen + KCl + H₂O + CO₂
 7.141 +    7.141 (mmol)
 714.9 +     1540 (mg)

NaHCO₃ + Phen-HCl → Phen + NaCl + H₂O + CO₂
 7.141 +    7.141 (mmol)
 599.9 +     1540 (mg)

Classes

Make your classes immutable via NamedTuple. This demonstrates a simple two-class system for substance definitions and reactant instances. You could throw in a lot more complexity, such as Equation, but that's not really needed.

from typing import NamedTuple

# Units
g = 1
mg = 1e3 * g
mol = 1
mmol = 1e3 * mol


class Substance(NamedTuple):
    short_name: str
    long_name: str
    g_mol: float  # molar mass

    def __str__(self) -> str:
        return self.short_name


class Reactant(NamedTuple):
    substance: Substance
    mol: float

    @classmethod
    def from_mass(cls, substance: Substance, g: float) -> 'Reactant':
        return cls(substance, g/substance.g_mol)

    @property
    def g(self) -> float:  # mass in grams
        return self.mol * self.substance.g_mol

    def __str__(self) -> str:
        return f'{self.substance}: {self.g * mg/g:.1f} mg'


KHCO3 = Substance('KHCO₃', 'potassium bicarbonate', 100.115)
NaHCO3 = Substance('NaHCO₃', 'sodium bicarbonate', 84.0066)
PhenHCl = Substance('Phen-HCl', 'phenibut hydrochloride', 215.67)


def main() -> None:
    phenhcl = Reactant.from_mass(PhenHCl, g=1.54)
    khco3 = Reactant(KHCO3, phenhcl.mol)
    nahco3 = Reactant(NaHCO3, phenhcl.mol)

    print('KHCO₃ + Phen-HCl → Phen + KCl + H₂O + CO₂')
    print(khco3)
    print(phenhcl)
    print()

    print('NaHCO₃ + Phen-HCl → Phen + NaCl + H₂O + CO₂')
    print(nahco3)
    print(phenhcl)


if __name__ == '__main__':
    main()
KHCO₃ + Phen-HCl → Phen + KCl + H₂O + CO₂
KHCO₃: 714.9 mg
Phen-HCl: 1540.0 mg

NaHCO₃ + Phen-HCl → Phen + NaCl + H₂O + CO₂
NaHCO₃: 599.9 mg
Phen-HCl: 1540.0 mg
\$\endgroup\$
5
  • 1
    \$\begingroup\$ very nice, much appreciated, I like it at a lot, will study this and I'll talk to you tomorrow, all that extra chemistry discourse caused by the comments which may have confused or caused curiosity among other users really tired me out it's 3am now, thanks a lot \$\endgroup\$
    – Nickotine
    Jun 27, 2023 at 1:56
  • \$\begingroup\$ Why remove decimal? Floats are fine but would it not be more accurate to use decimal? I'm just used to decimals. \$\endgroup\$
    – Nickotine
    Jun 27, 2023 at 18:50
  • 1
    \$\begingroup\$ There is no chance for this application to reveal noticeable error from floating-point math. You're many orders of magnitude away from that being a problem. Just do the simple thing and use floats until you have a reason otherwise, and when you do have a reason otherwise, decimal is not the only option. Depending on context, fractions may be more appropriate. \$\endgroup\$
    – Reinderien
    Jun 27, 2023 at 20:13
  • \$\begingroup\$ If I understand you correctly then you only need decimal when the number of figures becomes huge? If I don't then when would it be necessary or you'd have no other alternative but to use decimal? \$\endgroup\$
    – Nickotine
    Jun 27, 2023 at 21:11
  • \$\begingroup\$ There's an opportunity for this codebase to import unyts. Then "grams" or whatever will come along for the ride, appearing correctly in printed output and even in derived quantities following a multiply or divide. \$\endgroup\$
    – J_H
    Jun 30, 2023 at 18:45
6
\$\begingroup\$

Disclaimer: like most people I don't have any particular background in chemistry so I will focus on practical things from a programmer's perspective, at the risk of being naive.

There is no need to write a class for everything, a function is enough. The question is: how would you name it?

If your function returns a couple values, then it would make sense to return a namedtuple.

And if you are going to write a function, can it be reused with other substances? Possibly with additional arguments, like weighting factors? (my example must be dumb but you get the idea). I am not sure, since you are dealing with a very specific set of substances.

The decision to write a function becomes natural when there is intent of reuse, and to avoid repeating code. At this time, I cannot claim that you need to write a function, although it can be an exercise. If this is a one-time operation, there is no reason to.

Perhaps you'll want to perform additions or mix your results with other calculations, in which case it would be more convenient for your function to return floating-point numbers, or rather fractions instead of plain strings like '0.71g'. Then a docstring would be welcome to specify that the unit of reference is the gram. Or, if the result is simply proportional to the input values, the notion of gram carries no weight (if I dare say).

Is 1.54 a magic number? Then it could qualify as a constant of some sort and have a meaningful variable name.

Very minor point: os.linesep can be used instead of \n.

\$\endgroup\$
3
  • \$\begingroup\$ 0.71 is a float before I print it then it becomes a string which is enough for this purpose, it's just a small personal program. However If I do this properly I'd have it as a decimal thank you, so how do you print decimals/floats without them being strings? The reason why I had a name lambda is so I can show it's the moles equation. Valid point about there being no point in having the function aince it's only used once though. Purpose of posting this program was to see how much complication I need to make a class. \$\endgroup\$
    – Nickotine
    Jun 26, 2023 at 19:17
  • \$\begingroup\$ My question was answered by @reinderien, I was struggling with classes as my cases are too specific. I appreciate your input. 1.54g is just the dose of the nootropic and I thought I'd calculate the exact amount of the base to neutralise the very acidic nootropic, I was adding too much base, the HCO3- or bicarbonate can be doses to extreme levels with no consequence, however ionic potassium and sodium doses to high can cause low blood pressure or high blood pressure respectively although having had many tests, my bp, bloods and liver are all fine, just being extra careful. \$\endgroup\$
    – Nickotine
    Jun 26, 2023 at 19:24
  • \$\begingroup\$ Look at for my next post would appreciate your feedback. \$\endgroup\$
    – Nickotine
    Jun 26, 2023 at 21:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.