chemistrykit.polymer#

chemistrykit.polymer: polymer chemistry.

Ideal random-walk chain statistics (end-to-end distance, radius of gyration) and the Flory exponent for real chains under different solvent conditions; molecular-weight-distribution statistics (Mn, Mw, PDI) and the Flory-Schulz (most-probable) distribution derived from step-growth polymerization statistics; the Carothers equation for step-growth kinetics; and chain-growth/free-radical polymerization kinetics built on chemistrykit.kinetics’s reaction-network engine, with its steady-state-approximation closed form as a cross-check. Also Kuhn’s freely jointed chain and the Kratky-Porod worm-like chain; Flory-Huggins solution thermodynamics; Staudinger/Mark-Houwink solution viscosity; Debye light scattering vs. osmometry; Flory-Stockmayer gelation; the Mayo-Lewis copolymer equation; Poisson (living) chain-length distributions; and Bernoullian tacticity statistics.

class chemistrykit.polymer.IdealChain[source]#

Bases: PolymerChainModel

An ideal (theta-solvent) polymer chain: an exact random walk of n segments.

Examples

The mean-square end-to-end distance is exactly \(nb^2\) (Rubinstein & Colby, Polymer Physics, 2003, eq. 2.44):

>>> chain = IdealChain()
>>> chain.mean_square_end_to_end(n=100, b=0.5)
25.0

The radius of gyration is exactly \(R/\sqrt{6}\):

>>> import numpy as np
>>> n, b = 200, 0.6
>>> ratio = chain.mean_square_end_to_end(n, b) / chain.mean_square_radius_of_gyration(n, b)
>>> round(float(ratio), 6)
6.0

Doubling n exactly doubles \(\langle R^2\rangle\) (linear scaling, \(\nu=1/2\) since \(R^2\sim n^{2\nu}=n\)):

>>> chain.mean_square_end_to_end(n=400, b=0.5) / chain.mean_square_end_to_end(n=200, b=0.5)
2.0
mean_square_end_to_end(n, b)[source]#

Return \(\langle R^2\rangle\) for a chain of n segments of length b.

Parameters:
  • n (float or array-like of float) – Number of chain segments (statistical/Kuhn segments).

  • b (float) – Segment (Kuhn) length.

Returns:

float or ndarray

mean_square_radius_of_gyration(n, b)[source]#

Return \(\langle R_g^2\rangle\) for a chain of n segments of length b.

Parameters:
Returns:

float or ndarray

class chemistrykit.polymer.MolecularWeightDistribution(Mn, Mw, PDI)[source]#

Bases: object

Summary statistics (Mn, Mw, PDI) of a molar-mass distribution.

Parameters:
Mn: float#

Number-average molar mass.

Type:

float

Mw: float#

Weight-average molar mass.

Type:

float

PDI: float#

Polydispersity index, Mw/Mn.

Type:

float

classmethod from_counts(N_i, M_i)[source]#

Build from raw (count, molar mass) fraction data.

Parameters:
Return type:

MolecularWeightDistribution

Returns:

MolecularWeightDistribution

Examples

>>> mwd = MolecularWeightDistribution.from_counts(N_i=[10.0, 5.0], M_i=[1000.0, 2000.0])
>>> round(mwd.Mn, 2), round(mwd.Mw, 2)
(1333.33, 1500.0)
>>> round(mwd.PDI, 4)
1.125
class chemistrykit.polymer.PolymerChainModel[source]#

Bases: ABC

Common interface for a polymer chain’s characteristic size as a function of chain length.

Concrete subclasses (chemistrykit.polymer.systems.chain_statistics.IdealChain, chemistrykit.polymer.systems.chain_statistics.RealChain) implement mean_square_end_to_end() and mean_square_radius_of_gyration(); end_to_end_distance() and radius_of_gyration() (their square roots) are then available for every subclass for free.

end_to_end_distance(n, b)[source]#

Return \(R = \sqrt{\langle R^2\rangle}\).

Parameters:
Returns:

float or ndarray

abstractmethod mean_square_end_to_end(n, b)[source]#

Return \(\langle R^2\rangle\) for a chain of n segments of length b.

Parameters:
  • n (float or array-like of float) – Number of chain segments (statistical/Kuhn segments).

  • b (float) – Segment (Kuhn) length.

Returns:

float or ndarray

abstractmethod mean_square_radius_of_gyration(n, b)[source]#

Return \(\langle R_g^2\rangle\) for a chain of n segments of length b.

Parameters:
Returns:

float or ndarray

radius_of_gyration(n, b)[source]#

Return \(R_g = \sqrt{\langle R_g^2\rangle}\).

Parameters:
Returns:

float or ndarray

class chemistrykit.polymer.RealChain(nu)[source]#

Bases: PolymerChainModel

A real (excluded-volume) polymer chain: \(R = bn^\nu\) with Flory exponent nu.

Parameters:

nu (float) – Flory scaling exponent. Use theta_solvent(), good_solvent(), or poor_solvent() for the standard named values rather than passing nu directly, unless modeling a nonstandard solvent quality.

Examples

A real chain in a good solvent is more swollen (larger) than an ideal chain of the same n:

>>> ideal = IdealChain()
>>> good = RealChain.good_solvent()
>>> n, b = 1000, 0.5
>>> bool(good.end_to_end_distance(n, b) > ideal.end_to_end_distance(n, b))
True

A real chain in a poor solvent is more collapsed (smaller):

>>> poor = RealChain.poor_solvent()
>>> bool(poor.end_to_end_distance(n, b) < ideal.end_to_end_distance(n, b))
True

At the theta point, RealChain reduces exactly to the ideal chain’s end-to-end distance (though not, by construction here, to its exact radius-of-gyration prefactor – see the module docstring):

>>> theta = RealChain.theta_solvent()
>>> round(float(theta.end_to_end_distance(n, b)), 6) == round(float(ideal.end_to_end_distance(n, b)), 6)
True
classmethod good_solvent()[source]#

Build a RealChain with Flory’s mean-field good-solvent exponent (nu=3/5).

See good_solvent_renormalization_group() for the more precise renormalization-group value.

Return type:

RealChain

classmethod good_solvent_renormalization_group()[source]#

Build a RealChain with the renormalization-group good-solvent exponent (nu ~= 0.588).

This is de Gennes’ (1979) more precise value, refining Flory’s original mean-field estimate (good_solvent(), nu=3/5) by about 2% – see the module docstring.

Return type:

RealChain

Examples

>>> flory = RealChain.good_solvent()
>>> de_gennes = RealChain.good_solvent_renormalization_group()
>>> relative_difference = abs(flory.nu - de_gennes.nu) / flory.nu
>>> round(float(relative_difference) * 100, 2)
2.0
mean_square_end_to_end(n, b)[source]#

Return \(\langle R^2\rangle\) for a chain of n segments of length b.

Parameters:
  • n (float or array-like of float) – Number of chain segments (statistical/Kuhn segments).

  • b (float) – Segment (Kuhn) length.

Returns:

float or ndarray

mean_square_radius_of_gyration(n, b)[source]#

See the module docstring: the 1/6 prefactor is an ideal-chain approximation, not exact for real chains.

classmethod poor_solvent()[source]#

Build a RealChain with the poor-solvent Flory exponent (nu=1/3).

Return type:

RealChain

classmethod theta_solvent()[source]#

Build a RealChain with the theta-solvent Flory exponent (nu=1/2).

Return type:

RealChain

chemistrykit.polymer.azeotropic_feed_composition(r1, r2)[source]#

Azeotropic feed \(f_1^*=(1-r_2)/(2-r_1-r_2)\) at which \(F_1=f_1\).

Parameters:
  • r1 (float) – Reactivity ratios (both < 1 or both > 1).

  • r2 (float) – Reactivity ratios (both < 1 or both > 1).

Return type:

float

Returns:

float

Examples

Styrene (1) / methyl methacrylate (2), \(r_1\approx0.52\), \(r_2\approx0.46\):

>>> fstar = azeotropic_feed_composition(0.52, 0.46)
>>> round(fstar, 4)
0.5294
>>> round(float(mayo_lewis_copolymer_composition(fstar, 0.52, 0.46)), 4)
0.5294
chemistrykit.polymer.bernoullian_triad_fractions(Pm)[source]#

Triad fractions \(([mm],[mr],[rr])\) for Bernoullian placement with meso probability Pm.

Parameters:

Pm (float or array-like of float) – Probability of a meso placement.

Return type:

tuple

Returns:

mm, mr, rr (float or ndarray) – They sum to 1.

Examples

Atactic (\(P_m=1/2\)) gives the 1:2:1 ratio:

>>> tuple(round(float(v), 3) for v in bernoullian_triad_fractions(0.5))
(0.25, 0.5, 0.25)
chemistrykit.polymer.branching_number_average_DP(p, f)[source]#

Number-average degree of polymerization of \(A_f\) self-condensation, \(1/(1-fp/2)\).

Parameters:
  • p (float or array-like of float) – Extent of reaction (fraction of groups reacted), below the gel point.

  • f (float) – Functionality.

Returns:

float or ndarray

Examples

Finite at the Flory-Stockmayer gel point, \(2(f-1)/(f-2)\):

>>> round(float(branching_number_average_DP(0.5, f=3)), 6)
4.0
chemistrykit.polymer.branching_weight_average_DP(p, f)[source]#

Weight-average degree of polymerization of \(A_f\) self-condensation, \((1+p)/(1-(f-1)p)\).

Parameters:
  • p (float or array-like of float) – Extent of reaction, \(p<p_c\).

  • f (float) – Functionality.

Returns:

float or ndarray

Examples

With f=2 this is the Flory-Schulz \((1+p)/(1-p)\):

>>> round(float(branching_weight_average_DP(0.9, f=2)), 6)
19.0
chemistrykit.polymer.carothers_gel_point(f_avg)[source]#

Carothers gel point, \(p_c=2/f_\text{avg}\) (where \(\bar X_n\to\infty\)).

Parameters:

f_avg (float) – Average monomer functionality.

Return type:

float

Returns:

float

Examples

>>> round(carothers_gel_point(3.0), 6)
0.666667
chemistrykit.polymer.debye_Kc_over_R(c, Mw, A2=0.0)[source]#

Debye relation \(Kc/R_0=1/M_w+2A_2c\) (zero scattering angle).

Parameters:
  • c (float or array-like of float) – Total polymer mass concentration.

  • Mw (float) – Weight-average molar mass.

  • A2 (float) – Second virial coefficient.

Returns:

float or ndarray

Examples

>>> float(debye_Kc_over_R(0.0, Mw=1e5, A2=1e-4))
1e-05
chemistrykit.polymer.degree_of_polymerization(p)[source]#

The Carothers equation: \(\bar{X}_n=1/(1-p)\).

Parameters:

p (float) – Extent of reaction (fraction of functional groups reacted), \(0\le p<1\).

Return type:

float

Returns:

float

Examples

At 90% conversion the average chain is 10 repeat units long:

>>> round(degree_of_polymerization(0.9), 6)
10.0

A “high” molecular weight polymer genuinely requires extreme conversion – reaching \(\bar X_n=100\) needs 99% conversion, not 90%:

>>> round(degree_of_polymerization(0.99), 1)
100.0
chemistrykit.polymer.degree_of_polymerization_stoichiometric_imbalance(p, r)[source]#

Carothers equation generalized to a stoichiometric imbalance of the two functional groups.

When the two reactive functional groups (e.g. -OH and -COOH) are not present in exactly equal number – either a deliberate stoichiometric imbalance, or the deliberate addition of a monofunctional “chain stopper” – the maximum attainable degree of polymerization is capped even at \(p=1\) (Odian, Principles of Polymerization, 4th ed., Ch. 2.2, eq. 2-70):

\[\bar{X}_n = \frac{1+r}{1+r-2rp}\]

where \(r\le1\) is the ratio of the minority to majority functional-group count (or, with a monofunctional chain stopper of concentration \(N_B'\), \(r=N_A/(N_B+2N_B')\); see Odian for the full derivation). Setting \(r=1\) (perfect stoichiometric balance) recovers the ordinary degree_of_polymerization() exactly.

Parameters:
  • p (float) – Extent of reaction of the limiting (minority) functional group, \(0\le p\le1\).

  • r (float) – Stoichiometric ratio, \(0<r\le1\).

Return type:

float

Returns:

float

Examples

With r=1 this reduces exactly to the ordinary Carothers equation:

>>> round(degree_of_polymerization_stoichiometric_imbalance(p=0.9, r=1.0), 10)
10.0

A stoichiometric imbalance caps the attainable degree of polymerization even at complete conversion of the limiting group (\(p=1\)):

>>> round(degree_of_polymerization_stoichiometric_imbalance(p=1.0, r=0.98), 4)
99.0
chemistrykit.polymer.extent_of_reaction_for_DP(Xn)[source]#

Invert the Carothers equation: the extent of reaction needed for a target \(\bar X_n\).

Parameters:

Xn (float) – Target number-average degree of polymerization, \(\ge1\).

Return type:

float

Returns:

float – Required extent of reaction p.

Examples

Exactly inverts degree_of_polymerization():

>>> round(extent_of_reaction_for_DP(degree_of_polymerization(0.95)), 10)
0.95
chemistrykit.polymer.fit_mark_houwink(M, eta)[source]#

Fit \([\eta]=KM^a\) by least squares on \(\log[\eta]\) vs \(\log M\).

Parameters:
  • M (array-like of float) – Molar masses and measured intrinsic viscosities.

  • eta (array-like of float) – Molar masses and measured intrinsic viscosities.

Return type:

tuple[float, float]

Returns:

K, a (float)

Examples

>>> M = np.array([1e4, 1e5, 1e6])
>>> K, a = fit_mark_houwink(M, 0.02 * M**0.7)
>>> round(K, 6), round(a, 6)
(0.02, 0.7)
chemistrykit.polymer.flory_exponent(solvent)[source]#

Return the standard Flory scaling exponent \(\nu\) for a named solvent quality.

Parameters:

solvent (str) – Solvent quality (see the module docstring).

Return type:

float

Returns:

float

Examples

>>> flory_exponent("theta")
0.5
>>> round(flory_exponent("good"), 3)
0.6
>>> round(flory_exponent("poor"), 4)
0.3333
chemistrykit.polymer.flory_huggins_critical_point(N)[source]#

Critical point \((\phi_c,\chi_c)\) of a polymer solution.

Parameters:

N (float) – Degree of polymerization.

Return type:

tuple[float, float]

Returns:

phi_c, chi_c (float) – \(\phi_c=1/(1+\sqrt N)\) and \(\chi_c=\tfrac12(1+1/\sqrt N)^2\).

Examples

>>> phi_c, chi_c = flory_huggins_critical_point(100)
>>> round(phi_c, 6), round(chi_c, 6)
(0.090909, 0.605)

For very long chains \(\chi_c\) approaches the theta value 1/2:

>>> round(flory_huggins_critical_point(1e12)[1], 5)
0.5
chemistrykit.polymer.flory_huggins_free_energy(phi, N, chi)[source]#

Flory-Huggins free energy of mixing per lattice site, in units of \(k_BT\).

Parameters:
  • phi (float or array-like of float) – Polymer volume fraction, \(0<\phi<1\).

  • N (float) – Degree of polymerization (lattice sites per chain).

  • chi (float) – Flory-Huggins interaction parameter.

Returns:

float or ndarray

Examples

With N=1 and chi=0 this is the ideal mixing entropy of a symmetric binary mixture, \(\ln\frac12\) at \(\phi=1/2\):

>>> round(float(flory_huggins_free_energy(0.5, N=1, chi=0.0)), 6)
-0.693147
chemistrykit.polymer.flory_huggins_spinodal_chi(phi, N)[source]#

Interaction parameter on the spinodal, \(\chi_s=\frac12[1/(N\phi)+1/(1-\phi)]\).

Parameters:
  • phi (float or array-like of float) – Polymer volume fraction.

  • N (float) – Degree of polymerization.

Returns:

float or ndarray

Examples

For a symmetric blend (N=1) the spinodal at \(\phi=1/2\) is \(\chi=2\):

>>> float(flory_huggins_spinodal_chi(0.5, N=1))
2.0
chemistrykit.polymer.flory_schulz_number_average_DP(p)[source]#

Flory-Schulz number-average degree of polymerization \(\bar X_n=1/(1-p)\).

Identical to the Carothers equation, chemistrykit.polymer.systems.step_growth.degree_of_polymerization() – this is the same quantity derived from the chain-length distribution’s first moment rather than from a mass-balance argument.

Parameters:

p (float) – Extent of reaction, \(0\le p<1\).

Return type:

float

Returns:

float

Examples

>>> round(flory_schulz_number_average_DP(0.9), 6)
10.0
chemistrykit.polymer.flory_schulz_number_fraction(x, p)[source]#

Flory-Schulz number fraction of chains of length x: \(N_x=(1-p)p^{x-1}\).

Parameters:
  • x (int or array-like of int) – Chain length (degree of polymerization), \(x\ge1\).

  • p (float) – Extent of reaction, \(0\le p<1\).

Returns:

float or ndarray

Examples

The number fractions over all chain lengths sum to exactly 1 (a proper probability distribution):

>>> import numpy as np
>>> x = np.arange(1, 5000)
>>> round(float(np.sum(flory_schulz_number_fraction(x, p=0.98))), 6)
1.0
chemistrykit.polymer.flory_schulz_pdi(p)[source]#

Flory-Schulz polydispersity index \(\text{\dj}=\bar X_w/\bar X_n=1+p\).

Parameters:

p (float) – Extent of reaction, \(0\le p\le1\).

Return type:

float

Returns:

float – In \([1, 2]\).

Examples

The PDI is exactly 1 with no reaction (every “chain” is a lone monomer – trivially monodisperse):

>>> flory_schulz_pdi(0.0)
1.0

The PDI approaches exactly 2 in the high-conversion limit (\(p\to1\)) – the textbook result that an ideal step-growth polymerization’s molecular-weight distribution can never exceed a polydispersity of 2, however far the reaction is driven:

>>> flory_schulz_pdi(1.0)
2.0
chemistrykit.polymer.flory_schulz_weight_average_DP(p)[source]#

Flory-Schulz weight-average degree of polymerization \(\bar X_w=(1+p)/(1-p)\).

Parameters:

p (float) – Extent of reaction, \(0\le p<1\).

Return type:

float

Returns:

float

Examples

>>> round(flory_schulz_weight_average_DP(0.9), 6)
19.0
chemistrykit.polymer.flory_schulz_weight_fraction(x, p)[source]#

Flory-Schulz weight fraction of chains of length x: \(w_x=x(1-p)^2p^{x-1}\).

Parameters:
Returns:

float or ndarray

Examples

The weight fractions over all chain lengths sum to exactly 1:

>>> import numpy as np
>>> x = np.arange(1, 5000)
>>> round(float(np.sum(flory_schulz_weight_fraction(x, p=0.98))), 6)
1.0
chemistrykit.polymer.flory_stockmayer_gel_point(f)[source]#

Flory-Stockmayer gel point for \(A_f\) self-condensation, \(p_c=1/(f-1)\).

Parameters:

f (float) – Monomer functionality (> 2 for gelation at \(p<1\)).

Return type:

float

Returns:

float

Examples

>>> flory_stockmayer_gel_point(3)
0.5
chemistrykit.polymer.free_radical_network(kd, f, kp, kt, I0, M0, mode='combination')[source]#

Build the lumped initiation/propagation/termination free-radical polymerization network.

Species, in order: I (initiator), M (monomer), R (total radical concentration, lumped over all chain lengths), D (dead polymer chains formed by termination – a count of chains, not monomer units incorporated). The three reactions:

  1. I -> 2R at rate \((fk_d)[I]\) (so the net radical production rate is \(2fk_d[I]=R_i\), matching the standard definition above).

  2. R + M -> R at rate \(k_p[R][M]\) (propagation: consumes a monomer, leaves the radical count unchanged – the radical that reacted is regenerated one unit longer).

  3. 2R -> D (mode=”combination”, one dead chain per termination event) or 2R -> 2D (mode=”disproportionation”, two dead chains per event) at rate \(k_t[R]^2\).

Parameters:
  • kd (float) – Initiator decomposition rate constant.

  • f (float) – Initiator efficiency, \(0<f\le1\).

  • kp (float) – Propagation rate constant.

  • kt (float) – Termination rate constant.

  • I0 (float) – Initial initiator and monomer concentrations.

  • M0 (float) – Initial initiator and monomer concentrations.

  • mode (str) – Termination mechanism (affects only the D stoichiometry, not the radical/monomer dynamics).

Return type:

StoichiometricNetwork

Returns:

StoichiometricNetwork – Species ("I", "M", "R", "D").

Examples

Radical concentration quickly rises from zero and plateaus near the steady-state approximation’s prediction (see steady_state_radical_concentration()), while initiator and monomer are consumed only slowly on that timescale:

>>> kd, f, kp, kt = 1.0e-5, 0.5, 1.0e3, 1.0e7
>>> I0, M0 = 0.01, 5.0
>>> net = free_radical_network(kd, f, kp, kt, I0, M0)
>>> result = net.integrate((0.0, 50.0), dt=1e-2, method="rk4")
>>> R_ss = steady_state_radical_concentration(kd, f, I0, kt)
>>> bool(np.isclose(result.concentration("R")[-1], R_ss, rtol=0.05))
True
>>> bool(result.concentration("I")[-1] > 0.99 * I0)
True
chemistrykit.polymer.freely_jointed_chain(n, b, n_chains=1, rng=None)[source]#

Sample 3D conformations of Kuhn’s freely jointed chain: n bonds of length b in uniformly random directions.

Each bond vector is drawn independently and isotropically on the sphere of radius b (W. Kuhn, Kolloid-Z. 68, 2 (1934)); the chain starts at the origin. Averaged over many samples, the squared end-to-end distance converges to the exact ideal-chain result \(\langle R^2\rangle=nb^2\) (IdealChain), since the cross terms \(\langle\mathbf{b}_i\cdot\mathbf{b}_j\rangle\) vanish for independent bonds.

Parameters:
  • n (int) – Number of bonds (Kuhn segments).

  • b (float) – Bond (Kuhn) length.

  • n_chains (int) – Number of independent conformations to sample.

  • rng (int, numpy.random.Generator, or None, optional) – Seed or generator for reproducibility.

Return type:

ndarray

Returns:

ndarray, shape (n_chains, n + 1, 3) – Bead positions of every sampled chain.

Examples

Every bond has exactly length b:

>>> X = freely_jointed_chain(50, 0.5, n_chains=3, rng=0)
>>> X.shape
(3, 51, 3)
>>> bool(np.allclose(np.linalg.norm(np.diff(X, axis=1), axis=-1), 0.5))
True

The sample mean of \(R^2\) approaches \(nb^2=100\cdot1^2\):

>>> X = freely_jointed_chain(100, 1.0, n_chains=20000, rng=1)
>>> R2 = np.sum(X[:, -1] ** 2, axis=-1)
>>> bool(abs(R2.mean() / 100.0 - 1.0) < 0.03)
True
chemistrykit.polymer.kinetic_chain_length(kd, f, kp, kt, I, M)[source]#

Kinetic chain length \(\nu=R_p/R_i\): monomers consumed per radical generated.

\[\nu = \frac{R_p}{R_i} = \frac{k_p[M]}{2\sqrt{fk_dk_t[I]}}\]

directly related to the number-average degree of polymerization of the resulting dead polymer: \(\bar X_n=\nu\) for termination by disproportionation (each radical becomes one dead chain) or \(\bar X_n=2\nu\) for termination by combination (two radicals fuse into one dead chain) – see Odian, Principles of Polymerization, 4th ed., Ch. 3.3.2.

Parameters:
Returns:

float or ndarray

Examples

>>> nu = kinetic_chain_length(kd=1.0e-5, f=0.5, kp=1.0e3, kt=1.0e7, I=0.01, M=5.0)
>>> Ri = 2.0 * 0.5 * 1.0e-5 * 0.01
>>> Rp = steady_state_rate_of_polymerization(kd=1.0e-5, f=0.5, kp=1.0e3, kt=1.0e7, I=0.01, M=5.0)
>>> round(float(nu), 6) == round(float(Rp / Ri), 6)
True
chemistrykit.polymer.mark_houwink_exponent_from_flory(nu)[source]#

Mark-Houwink exponent implied by a Flory exponent via Flory-Fox, \(a=3\nu-1\).

Parameters:

nu (float) – Flory exponent (\(R\propto M^\nu\)).

Return type:

float

Returns:

float

Examples

>>> mark_houwink_exponent_from_flory(0.5)
0.5
>>> round(mark_houwink_exponent_from_flory(0.6), 6)
0.8
chemistrykit.polymer.mark_houwink_intrinsic_viscosity(M, K, a)[source]#

Mark-Houwink equation, \([\eta]=KM^a\).

Parameters:
  • M (float or array-like of float) – Molar mass.

  • K (float) – Mark-Houwink constants for a given polymer/solvent/temperature.

  • a (float) – Mark-Houwink constants for a given polymer/solvent/temperature.

Returns:

float or ndarray

Examples

>>> round(float(mark_houwink_intrinsic_viscosity(1e4, K=0.01, a=0.5)), 6)
1.0
chemistrykit.polymer.mayo_lewis_copolymer_composition(f1, r1, r2)[source]#

Instantaneous copolymer composition \(F_1\) from the Mayo-Lewis equation.

Parameters:
  • f1 (float or array-like of float) – Mole fraction of monomer 1 in the feed.

  • r1 (float) – Reactivity ratios.

  • r2 (float) – Reactivity ratios.

Returns:

float or ndarray

Examples

An ideal random copolymerization (\(r_1=r_2=1\)) has \(F_1=f_1\):

>>> float(mayo_lewis_copolymer_composition(0.3, 1.0, 1.0))
0.3

A perfectly alternating system (\(r_1=r_2=0\)) gives \(F_1=1/2\) at any feed:

>>> float(mayo_lewis_copolymer_composition(0.9, 0.0, 0.0))
0.5
chemistrykit.polymer.mean_isotactic_run_length(Pm)[source]#

Mean length of a run of consecutive meso dyads, \(1/(1-P_m)\) (Bernoullian).

Parameters:

Pm (float) – Meso placement probability, \(<1\).

Return type:

float

Returns:

float

Examples

>>> round(mean_isotactic_run_length(0.99), 6)
100.0
chemistrykit.polymer.number_average_molar_mass(N_i, M_i)[source]#

Number-average molar mass \(M_n=\sum N_iM_i/\sum N_i\).

Parameters:
  • N_i (array-like of float) – Number (or mole count) of chains in each molar-mass fraction.

  • M_i (array-like of float) – Molar mass of each fraction.

Return type:

float

Returns:

float

Examples

>>> number_average_molar_mass(N_i=[10.0, 5.0], M_i=[1000.0, 2000.0])
1333.3333333333333
chemistrykit.polymer.osmotic_pressure_dilute_mixture(c_i, M_i, T, R=8.314462618)[source]#

Van ‘t Hoff osmotic pressure of an ideal dilute mixture, \(\Pi=RT\sum c_i/M_i\).

Parameters:
  • c_i (array-like of float) – Mass concentrations (kg m^-3).

  • M_i (array-like of float) – Molar masses (kg mol^-1).

  • T (float) – Temperature (K).

  • R (float) – Gas constant.

Return type:

float

Returns:

float – Osmotic pressure (Pa).

Examples

The apparent molar mass \(RTc/\Pi\) is the number average:

>>> Pi = osmotic_pressure_dilute_mixture([1.0, 1.0], [10.0, 100.0], T=300.0)
>>> round(8.314462618 * 300.0 * 2.0 / Pi, 6)
18.181818
chemistrykit.polymer.poisson_number_average_DP(nu)[source]#

Number-average degree of polymerization, \(1+\nu\).

Parameters:

nu (float)

Return type:

float

Returns:

float

Examples

>>> poisson_number_average_DP(99.0)
100.0
chemistrykit.polymer.poisson_number_fraction(x, nu)[source]#

Poisson number fraction \(N_x=e^{-\nu}\nu^{x-1}/(x-1)!\) of chains with x units.

Parameters:
  • x (int or array-like of int) – Chain length (\(\ge1\)).

  • nu (float) – Mean number of monomers added per chain.

Returns:

float or ndarray

Examples

>>> import numpy as np
>>> round(float(np.sum(poisson_number_fraction(np.arange(1, 400), 50.0))), 10)
1.0
chemistrykit.polymer.poisson_pdi(nu)[source]#

Polydispersity index of the Poisson distribution, \(1+\nu/(1+\nu)^2\).

Parameters:

nu (float)

Return type:

float

Returns:

float

Examples

>>> round(poisson_pdi(99.0), 6)
1.0099
chemistrykit.polymer.poisson_weight_average_DP(nu)[source]#

Weight-average degree of polymerization, \((\nu^2+3\nu+1)/(\nu+1)\).

Parameters:

nu (float)

Return type:

float

Returns:

float

Examples

>>> round(poisson_weight_average_DP(1.0), 6)
2.5
chemistrykit.polymer.polydispersity_index(Mn, Mw)[source]#

Polydispersity index \(\text{\dj} = M_w/M_n\).

Parameters:
  • Mn (float) – Number- and weight-average molar mass.

  • Mw (float) – Number- and weight-average molar mass.

Return type:

float

Returns:

float – \(\ge 1\), with 1 for a perfectly monodisperse sample.

Examples

>>> polydispersity_index(Mn=1000.0, Mw=1000.0)
1.0
chemistrykit.polymer.rayleigh_ratio_dilute_mixture(c_i, M_i, K=1.0)[source]#

Zero-angle excess Rayleigh ratio of an ideal dilute mixture, \(R_0=K\sum c_iM_i\).

Parameters:
  • c_i (array-like of float) – Mass concentration of each species.

  • M_i (array-like of float) – Molar mass of each species.

  • K (float) – Optical constant.

Return type:

float

Returns:

float

Examples

The apparent molar mass \(R_0/(Kc)\) is the weight average:

>>> round(rayleigh_ratio_dilute_mixture([1.0, 1.0], [1e4, 1e5]) / 2.0, 6)
55000.0
chemistrykit.polymer.sample_dyad_sequence(n_dyads, Pm, rng=None)[source]#

Sample a Bernoullian dyad sequence: True for meso, False for racemo.

Parameters:
Return type:

ndarray

Returns:

ndarray of bool, shape (n_dyads,)

Examples

>>> s = sample_dyad_sequence(100000, 0.9, rng=0)
>>> bool(abs(s.mean() - 0.9) < 0.01)
True
chemistrykit.polymer.simulate_living_polymerization(n_chains, n_monomers, rng=None)[source]#

Stochastically grow n_chains living chains by adding n_monomers one at a time to random chains.

Each monomer attaches to a uniformly chosen active chain (equal reactivity, no termination), so chain lengths follow a multinomial distribution that tends to the Poisson distribution (poisson_number_fraction()) with \(\nu=\) n_monomers / n_chains.

Parameters:
  • n_chains (int) – Number of initiated chains (each starts with one unit).

  • n_monomers (int) – Number of monomers added in total.

  • rng (int, numpy.random.Generator, or None, optional) – Seed or generator.

Return type:

ndarray

Returns:

ndarray of int, shape (n_chains,) – Final chain lengths \(x\) (units per chain, including the first).

Examples

>>> x = simulate_living_polymerization(1000, 50000, rng=0)
>>> int(x.sum())
51000
chemistrykit.polymer.staudinger_specific_viscosity(c, M, Km)[source]#

Staudinger’s viscosity rule, \(\eta_\text{sp}=K_mcM\).

Parameters:
  • c (float or array-like of float) – Polymer concentration.

  • M (float or array-like of float) – Molar mass (or degree of polymerization).

  • Km (float) – Staudinger constant.

Returns:

float or ndarray

Examples

Doubling the chain length doubles the specific viscosity:

>>> float(staudinger_specific_viscosity(0.01, 2e5, 1e-4) / staudinger_specific_viscosity(0.01, 1e5, 1e-4))
2.0
chemistrykit.polymer.steady_state_radical_concentration(kd, f, I, kt)[source]#

Steady-state-approximation (SSA) radical concentration \([M^\bullet]_{ss}=\sqrt{fk_d[I]/k_t}\).

Setting the radical production rate \(R_i=2fk_d[I]\) equal to the termination (radical-consumption) rate \(R_t=2k_t[M^\bullet]^2\) and solving for \([M^\bullet]\) gives this result (Odian, Principles of Polymerization, 4th ed., Ch. 3.3.1, eq. 3-21) – valid whenever radical production and consumption equilibrate on a timescale much shorter than the initiator/monomer are consumed (\(k_t[M^\bullet]\gg k_d\), essentially always true in practice since termination is diffusion-controlled while initiator decomposition is not).

Parameters:
  • kd (float) – Initiator decomposition rate constant.

  • f (float) – Initiator efficiency.

  • I (float or array-like of float) – Initiator concentration.

  • kt (float) – Termination rate constant.

Returns:

float or ndarray

Examples

>>> round(float(steady_state_radical_concentration(kd=1.0e-5, f=0.5, I=0.01, kt=1.0e7)), 10)
7.07e-08
chemistrykit.polymer.steady_state_rate_of_polymerization(kd, f, kp, kt, I, M)[source]#

Steady-state rate of polymerization \(R_p=k_p[M]\sqrt{fk_d[I]/k_t}\).

The rate at which monomer is consumed by propagation, evaluated at the steady-state radical concentration (steady_state_radical_concentration()) – the classic result that \(R_p\) scales as the square root of initiator concentration (Odian, Principles of Polymerization, 4th ed., Ch. 3.3.1, eq. 3-22), a distinctive experimental signature of free-radical (as opposed to, e.g., ionic) chain polymerization.

Parameters:
Returns:

float or ndarray

Examples

Doubling the initiator concentration increases the rate by only \(\sqrt2\), not 2 – the square-root dependence:

>>> kd, f, kp, kt, M = 1.0e-5, 0.5, 1.0e3, 1.0e7, 5.0
>>> Rp1 = steady_state_rate_of_polymerization(kd, f, kp, kt, I=0.01, M=M)
>>> Rp2 = steady_state_rate_of_polymerization(kd, f, kp, kt, I=0.02, M=M)
>>> round(float(Rp2 / Rp1), 6)
1.414214
chemistrykit.polymer.weight_average_molar_mass(N_i, M_i)[source]#

Weight-average molar mass \(M_w=\sum N_iM_i^2/\sum N_iM_i\).

Parameters:
Return type:

float

Returns:

float

Examples

\(M_w \ge M_n\) always (Cauchy-Schwarz), with equality only for a monodisperse sample:

>>> N_i, M_i = [10.0, 5.0], [1000.0, 2000.0]
>>> weight_average_molar_mass(N_i, M_i) >= number_average_molar_mass(N_i, M_i)
True
chemistrykit.polymer.worm_like_chain_mean_square_end_to_end(L, P)[source]#

Kratky-Porod worm-like chain mean-square end-to-end distance.

For a semiflexible chain of contour length \(L\) whose tangent correlations decay as \(e^{-s/P}\) along the contour (persistence length \(P\)), Kratky and Porod (Recl. Trav. Chim. Pays-Bas 68, 1106 (1949)) obtained

\[\langle R^2\rangle = 2PL\left[1-\frac{P}{L}\left(1-e^{-L/P}\right)\right]\]

which interpolates between a rigid rod, \(\langle R^2\rangle\to L^2\) for \(L\ll P\), and an ideal random coil, \(\langle R^2\rangle\to2PL\) for \(L\gg P\) – i.e. a freely jointed chain with Kuhn length \(b=2P\).

Parameters:
  • L (float or array-like of float) – Contour length (> 0).

  • P (float) – Persistence length (> 0).

Returns:

float or ndarray

Examples

A very short chain is a rigid rod, \(R^2\approx L^2\):

>>> round(float(worm_like_chain_mean_square_end_to_end(1e-3, 50.0) / 1e-6), 4)
1.0

A very long chain is an ideal coil with Kuhn length \(2P\):

>>> round(float(worm_like_chain_mean_square_end_to_end(1e6, 50.0) / (2 * 50.0 * 1e6)), 4)
1.0