PhysEq simplifies the process of creating realistic physics problems with step-by-step solutions at the high school and introductory college levels. You specify the setup and the relevant equations, and PhysEq solves for unknowns and creates solutions. There is built-in support for randomizing values, with safeguards against unphysical values.
Currently, there is not built-in support for calculus.
-
Symbols, equations, and algebra for solutions are based on SymPy.
-
Equations and algebra track expression order. When you define equations, you are also defining your preferred order for symbols, terms, and factors. For example, if you define an equation as
$F=ma$ , then it will appear as$F=ma$ in solutions. The appearance of equations is not based on the ordering used internally within the computer algebra system (similar to alphabetical order), so$F=ma$ is not transformed into$F=am$ in solutions. This makes solutions much easier to read. -
Physical values are represented with
Quantityfrom Astropy.Quantitycombines a numerical value with a unit and has built-in support for unit conversions. PhysEq checks inputs to ensure that they have appropriate units. Inputs are automatically converted into standard SI units before numerical calculations. -
When numerical values are substituted into symbolic equations, values are checked against mathematical assumptions and any additional value constraints that have been defined. For example, masses and vector magnitudes cannot be negative, so attempting to replace them with negative numbers results in an error. Similarly, functions for generating randomized quantities raise an error for unphysical values.
Examples are under examples/ in the project repository. Here is example
output for a friction problem:
A mass
Known
$g = 9.80 \, \mathrm{\tfrac{m}{s^{2}}}\quad v = 0 \, \mathrm{\tfrac{m}{s}}\quad m = 22.0 \, \mathrm{kg}\quad \mu_{k} = 0.154\quad v_{0} = 8.10 \, \mathrm{\tfrac{m}{s}}$
Unknown
$d$
Solutions
-
$\displaystyle W_{\text{nc}} = \Delta \text{KE} + \Delta \text{PE}$ $\displaystyle W_{\text{nc}} = \Delta \text{KE}$ $\displaystyle - F_{k} d = \text{KE} - \text{KE}_{0}$ $\displaystyle - \mu_{k} F_{N} d = \frac{1}{2} m v^{2} - \frac{1}{2} m v_{0}^{2}$ $\displaystyle - \mu_{k} m g d = \frac{1}{2} m v^{2} - \frac{1}{2} m v_{0}^{2}$ $\displaystyle d = \frac{- \frac{1}{2} m v^{2} + \frac{1}{2} m v_{0}^{2}}{\mu_{k} m g}$ $\displaystyle d = \frac{- v^{2} + v_{0}^{2}}{2 \mu_{k} g}$ $d = \frac{- \left(0\right)^{2} + \left(8.10\right)^{2}}{2 \left(0.154\right) \left(9.80\right)}$ $\displaystyle d = 21.7 \, \mathrm{m}$
The examples under examples/ in the project repository may be a good place
to start in creating problems.
Problems are created with the Problem class:
problem = Problem(
setup: dict[<symbols>, <numbers or quantities>],
knowns: list[<symbols>],
unknowns: list[<symbols>],
equations: list[<equation>],
definitions: list[<equation>] | None = None,
simplify: bool = False,
)
setup provides a mapping of symbols to numerical values that allows a
complete problem to be generated. It may include symbols that will ultimately
be used as unknowns. The goal at this stage is to generate a complete set of
values, regardless of which ones will ultimately treated as unknowns. In many
cases, it is simpler to create a physically realistic problem by starting with
quantities such as velocity and mass, even if one or more of these are
ultimately treated as unknowns.
setup values may be numbers, in which case they are interpreted as values in
the standard SI units for the corresponding symbols. Alternatively, values may
be provided as Astropy Quantity, in which case they are automatically
converted into standard SI units before use.
knowns are the symbols whose values will be given in the problem.
unknowns are the symbols that must be solved for.
equations are the primary equations to be used in solving. This list would
typically include fundamental equations such as
Currently, there is only support for problems that can be solved as a sequence of solving equations with one unknown. For example, an equation might have two unknowns, but it is possible to solve for one of these using a separate equation with only one unknown, so ultimately the problem only involves solving equations with one unknown. Limited support for problems involving systems of equations with multiple unknowns may be added in the future.
definitions are substituted into equations as needed, but typically would
not appear independently in step-by-step solutions. They may be simple
equations that define terms in equations (such as <symbol>=<expression>, where
<symbol> appears in equations.
By default, step-by-step solutions use minimal simplifications, so that they
will be easier to follow and maintain a form closer to the starting equations.
This can be changed by setting simplify=True.
When a problem is created with problem = Problem(...), there are three
relevant attributes for converting problem into a form that can be used in
teaching.
-
problem.knowns: dict[<symbol>, <quantity>]: This maps known symbols to the quantities that are given. Quantities are instances of AstropyQuantity, so they have both a numerical value and a unit. -
problem.unknowns: dict[<symbol>, <quantity>]: This maps unknown symbols to the quantities that should be found. -
problem.equations: dict[<equation>, list[<equation>]]: This maps equations to step-by-step solutions. Solutions are represented as lists of equations.
There is also a method problem.simple_solutions() that creates simple
solutions in Markdown format.
physeq.cas.printing provides a latex() function for converting symbols,
equations, and quantities into strings in LaTeX format, compatible with LaTeX
documents and HTML libraries like MathJax and KaTeX.
PhysEq includes an expanding library of pre-defined physics symbols
(physeq.symbols.<physics_category>) and equations
(physeq.<physics_category>).
Some physical quantities do not have completely standardized representations
in introductory physics materials. For example, kinetic energy commonly
appears as either physeq.config.kinetic_energy = 'E_k' (or using another definition). It is
also possible to customize config.potential_energy (default config.coulombs_constant (default config.spring_constant
(default
Most symbols support subscripting. For example, for a problem involving
multiple masses, additional mass symbols can be defined from the predefined
mass symbol m via m_1 = m.subscript(1), m_2 = m.subscript(2), and so
forth. This is typically simpler than creating new symbols from scratch.
Additional custom symbols should be created using physeq.cas.wrapped.Symbol.
Additional equations should be created using physeq.cas.wrapped.Eq. When
possible, SymPy representations of numbers (for example,
Rational) and functions (for example, sqrt and cos) should not be used
directly; the wrappers in physeq.cas.wrapped should be used instead. These
Symbol and Eq classes along with the wrappers track expression order, so
that solutions will be easier to follow. Without expression order tracking,
equations would appear with symbols sorted in something like alphabetical
order (for example,
Symbols include built-in support for randomization. (This does not apply to
symbols representing physical constants.) For example,
m.randint_quantity(1, 10) might return the Astropy Quantity 2 kg.
There are four methods for generating randomized quantities. All of these
build on Python's
random module. These
methods select a random number, and then create a Quantity by combining that
number with the symbol's standard SI unit.
-
randrange_quantity(start, stop[, step]): Create aQuantityusing a random integer fromrange(start, stop, step). -
randint_quantity(first, last): Create aQuantityusing a random integerNselected so thatfirst <= N <= last. -
choice_quantity(seq): Randomly select a number from the sequenceseq, and create aQuantityfrom it. -
uniform_quantity(a, b): Randomly select a floating-point number betweenaandb(inclusive of both), and create aQuantityfrom it.
When quantities are randomly generated using these methods, the resulting numerical values are checked against mathematical assumptions and any additional value constraints that have been defined. An error is raised for unphysical values.
Symbols are defined with built-in mathematical assumptions. For example, masses and vector magnitudes cannot be negative, and this is enforced in solutions.
Sometimes symbols need additional constraints. For example, final velocity
might be positive or negative depending on the time interval we are
considering. Additional constraints can be specified for symbols in knowns
and unknowns. It can be useful to add constraints to symbols in knowns
because the symbols in setup may not overlap with them.
For example, final velocity in y can be constrained as positive in knowns or
unknowns by using v_y.constrain_positive(). This might appear in code
like this:
Problem(
...
unknowns=[v_y.constrain_positive(), Δy],
...
)
There are currently six possible constraints:
constrain_positive()constrain_nonnegative()constrain_negative()constrain_nonpositive()constrain_nonzero()constrain_zero()