Giter Site home page Giter Site logo

forallpeople's Introduction

forallpeople:
The world's units
at your fingertips

"For all people. For all time."
Nicolas de Caritat (Marquis de Condorcet),
in regards to the creation of the metric (SI) system.

forallpeople is a Python library for providing access to units-aware calculations through the SI units system, and other units defined by the SI sytem (e.g. US customary units, Imperial).

While there are many other units libraries available for Python, forallpeople is unique in the following ways:

  1. Physical quantities are automatically reduced during multiplication and division.
  2. Physical quantities that are completely cancelled out return just a number (instead of a dimensionless unit).
  3. Physical quantities carry their own dimension vectors and, as such, their definitions exist independently of the units environment they are in.
  4. When loaded, the units variables can either be dynamically pushed to the module namespace or to the top-level namespace. This allows for faster code writing that looks like this a = 5 * kg instead of a = 5 * si.kg
  5. Released on PyPI on the day the new definition of the kilogram was adopted by the world on World Metrology Day, 20 May 2019.

Installing

You can install using pip:

pip install forallpeople

Basic usage 1: Module-level namespace

The most basic use is just to import the library:

import forallpeople as si

This will import the Physical class. It is the primary class in the module and is used to describe all physical quantities. Physical instances are immutable.

Upon import, the SI base units are instantiated and are available in the namespace as the following variables:

  • si.m - meter
  • si.kg - kilogram
  • si.s - second
  • si.A - ampere
  • si.cd - candela
  • si.K - kelvin
  • si.mol - mole

Basic Usage 1

Loading an environment

si.environment('default', [top_level=False])

This will load the 'default.json' file within the forallpeople module that describes the SI derived units, the units created by compounding the base units (e.g. Newton, Pascal, Celsius, Watt, Joule, etc.).

When you load an environment, whether it is the default environment or one you define, the representation of the units will change to fit the definitions in the environment. Environment definitions are dimensional, meaning, if you end up with a Physical of a dimension that matches a dimension in the environment, then the repr() of that instance will change to match the dimensioned unit in the environment.

It is important to note that, no matter what environment is loaded or not loaded, your Physical instances will always carry their value in the SI base units, e.g.:

>>> pressure = force / area
>>> pressure = 208.333 Pa
>>> pressure.repr
>>> 'Physical(value=208.33333333333334, dimensions=Dimensions(kg=1, m=-1, s=-2, A=0, cd=0, K=0, mol=0), factor=1, _precision=3)'

Additionally, when you load an environment, the units defined in the environment will be instantiated as Physical and you can utilize them as variables in calculations.

The 'default' environment defines and loads the following units as variables into the module namespace:

  • si.N - newton
  • si.Pa - pascal
  • si.J - joule
  • si.W - watt
  • si.C - coulomb
  • si.V - volt
  • si.F - farad
  • si.Ohm - ohm
  • si.S - siemens
  • si.Wb - weber
  • si.T - tesla
  • si.H - henry
  • si.lm - lumen
  • si.lx - lux
  • si.Bq - becquerel
  • si.Gy - gray
  • si.Sv - sievert
  • si.kat - katal

Because the units of si.N are one of the Physical instances that have now been instantiated and loaded into the si namespace, you can perform calculations with them directly:

>>> area = 3*si.m * 4*si.m
>>> force = 2500 * si.N
>>> force / area
>>> 208.333 Pa

Basic usage 2: Top-level namespace

Everything from Basic usage 1 applies, except that, when loading an environment by setting top_level=True, all of the units described in the environment json file will instead be "pushed" into the top-level namespace, i.e. you would not type 5 * si.N but 5 * N.

Basic Usage 2

API

Each Physical instance offers the following methods and properties:

Properties

  • .value: A float that represents the numerical value of the physical quantity in SI base units
  • .dimensions: A Dimensions object (a NamedTuple) that describes the dimension of the quantity as a vector
  • .factor: A float that represents a factor that the value should be multiplied by to linearly scale the quantity into an alternate unit system (e.g. US customary units or UK imperial) that is defined in SI units.
  • .latex: A str that represents the pretty printed repr() of the quanity in latex code.
  • .html: A str that represents the pretty printed repr() of the quantity in HTML code.
  • .repr: A str that represents the traditional machine readable repr() of the quantity: Physical instances default to a pretty printed __repr__() instead of a machine readable __repr__() because it makes them more compatible with other libraries (e.g. numpy, pandas, handcalcs, and jupyter).

Methods

Almost all methods return a new Physical because all instances are immutable.

  • .round(self, n: int): Returns a Physical instance identical to self except with the display precision set to n. You can also call the python built-in round() on the instance to get the same behaviour.
  • .sqrt(self, n: float): Returns a Physical that represents the square root of self. n can be set to any other number to compute alternate roots.
  • .split(self): Returns a 2-tuple where the 0-th element is the .value of the quantity and the 1-th element is the Physical instance with a value set to 1 (i.e. just the dimensional part of the quantity). To reconstitute, multiply the two tuple elements together. This is useful to perform computations in numpy that only accept numerical input (e.g. numpy.linalg.inv()): the value can be computed separately from the dimension and then reconstituted afterwards.
  • .to(self, unit_name: str = ""): Returns a new Physical instance with a .factor corresponding to a dimensionally compatible unit defined in the environment. If .to() is called without any arguments, then a list of available units for that quantity is printed to stdout.

Dimensionally inconsistent calculations

It is not uncommon for some calculations to use formulas whose dimensions seem to magically appear on their own. The forallpeople library can handle these calculations if the "hidden dimensions" are recognized and accounted for by the user.

Example: in the Canadian concrete design code it is recognized that sqrt(1*MPa) results in units of MPa instead of MPa0.5. Here is one way this can be managed in forallpeople:

>>> import forallpeople as si
>>> from math import sqrt
>>> si.environment('default')
>>> MPa = 1e6 * si.Pa
>>> f_c = 35 * MPa
>>> sqrt(f_c) * MPa
5.916 MPa

This behaviour occurs because of the way __float__ is defined in the Physical class: if float() is called on a Physical, the returned value will be the numerical portion of the auto-reduced, auto-prefixed unit representation.

Example:

>>> import forallpeople as si
>>> from math import sqrt
>>> si.environment('default')
>>> MPa = 1e6 * si.Pa
>>> f_c = 35 * MPa
>>> f_c # As expected
35.000 MPa
>>> float(f_c) # The numerical portion of the prefixed unit
35.0
>>> f_c.value # However, the actual value is 35e6 Pa
35000000.0
>>> sqrt(f_c) # Sqrt of "35"
5.916079783099616
>>> sqrt(f_c.value) # Sqrt of "35000000.0"
5916.079783099616

Many of Python's math functions will first attempt to call float() on an argument that is not already a float. forallpeople takes advantage of this by ensuring the float value returned is the same number you would see in the unit representation. If you prefer the operation be performed on the base unit value, then simply substitute the .value value as the function argument.

How Physical instances work

forallpeople is for describing physical quantities and defines a single class, Physical, to represent them. Physical instances are composed of four components (as attributes):

  • .value: a float that is the numerical value of the quantity in the SI base units.
  • .dimensions: a NamedTuple, called Dimensions, that describes the dimensionality of the physical quantity in terms of the SI base units.
  • .factor: a float that can be used to define a physical quantity in an alternate unit system that is linearly based upon the SI units (e.g. US customary units, imperial, etc.).
  • .precision: an int that describes the number of decimal places to display when the Physical instance is rendered through .__repr__(), default value is 3.
  • .prefixed: a str that represents a prefix abbreviation in the SI units system, e.g. "k" for "kilo", "n" for "nano". This allows the user to over-ride .

Because Physical instances are immutable (just like int, float, str, and bool), the user cannot set these attributes directly. It also means that any arithmetic operation on a Physical instance returns a new instance. Arithmetic operations is the intended way of creating new Physical instances.

Dimension vectors

Physical instances track the dimensions of their physical quantities by using vectors. The vector is stored in the Dimensions class, which is a NamedTuple. Using the vector library, tuplevector (which is "baked in" to forallpeople), vector arithmetic is performed on Dimensions objects directly.

Arithmetic on Physical instancess

Arithmetic on Physical instances work mostly how you would expect, with few caveats:

  • Addition and subtraction:
    • Two (or more) instances will add/sub if dimensions are equal.
    • One instance and one (or more) number(s) (float, int) will add/sub and assume the units of the instance.
    • e.g. a = 5*si.m + 2*si.m, b = 5*si.kg + 10.
  • Multiplication:
    • Instances will multiply with each other and their dimensions will combine.
    • Instances will multiply with numbers and will assume the units of instance(s) that were also a part of the multiplication.
    • e.g. c = 12 *si.m * 2*si.kg * si.s, d = 4.5*si.m * 2.3.
  • Division (true division):
    • Instances will divide by each other and the difference of their dimension vectors will be the dimensions of the new instance.
    • Instances can also divide with numbers and will assume the units of the instance(s) that were also a part of the division.
    • If two instances of the same dimension are divided, the result will be a float (i.e. the units are completely cancelled out; there is no "dimensionless" Physical: either a quantity has units as a Physical or it is a number).
    • e.g. 5 * si.m / (2 * si.m) -> 2.5
  • Floor division:
    • Is intentionally not implemented in Physical. This is because it creates ambiguity when working within an environment where units with factors are defined. (Does floor division return the value of floor division of the SI base unit value or the apparent value after multiplied by it's .factor? Either would return results that may be unexpected.)
    • Floor division can be achieved by using true division and calling int() on the result, although this returns an int and not a Physical instance.
  • Power:
    • You can raise an instance to any power, if it is a number (int, float). You cannot raise a Physical instance to the power of another instance (what would that even mean?).
  • Abs:
    • Returns the absolute value of the instance.
  • Neg:
    • Equivalent to instance * -1.

Auto-prefixing

forallpeople employs "auto-prefixing" and by default selects the most conventional way of representing the unit, scaled to an appropriate prefix.

>>> current = 0.5 * A
>>> current
500.000 mA # 'current' is auto-prefixed to 500 milliamperes
>>> resistance = 1200 * Ohm
>>> resistance
1.200  # 'resistance' is auto-prefixed to kilo-ohms
>>> voltage = current * resistance
>>> voltage
600.000 V # 'voltage' does not have a prefix because its value is above 1 V but less than 1000 V

The prefixes of the entire SI units system (from 10**-24 to 10**24) are built-in to the Physical class and they step at every three powers of ten (i.e. 10**-3, 10**0, 10**3, 10**6, etc.).

However, auto-prefixing is only triggered in certain, intuitive circumstances:

  1. The unit is one of m, kg, s, A, cd, K, or mol (i.e. the SI base units).
  2. The unit is a derived unit in the SI unit system (i.e. it is defined in the environment and has a .factor == 1).

This means that auto-prefixing is not used in the following circumstances:

  1. The unit is defined in the environment with a factor (e.g. lb: it would not make sense to have a klb or a mlb).
  2. The unit is a compound unit but not defined in the environment (e.g. it would not make sense to have a kkg*m/s).

When the auto-prefixing is triggered for a unit and that unit is of a power other than 1, then auto-prefixing considers the prefix to also be part of the unit's power. For example:

>>> a = 5000 * si.m
>>> a
5.000 km
>>> a**2
25.000 km² # Remember that the 'kilo' prefix is also being squared
>>> b = 500000 * si.m 
>>> b
500.000 km
>>> b**2
250000.000 km² # Why isn't this being shown as 250 Mm²? Because it would take 1,000,000 km² to make a Mm². This is only 250,000 km².

How to define your own environments

An environment is simply a JSON document stored within the package folder in the following format:

"Name": {
    "Dimension": [0,0,0,0,0,0,0],
    "Value": 1,
    "Factor": 1,
    "Symbol": ""}

For example, if you wanted to create an environment that defined only kilopascals and pounds-force in US customary units, you would do it like this:

"kPa": {
    "Dimension": [1,-1,-2,0,0,0,0],
    "Value": 1000},
"lb-f": {
    "Dimension": [1, 1, -2, 0, 0, 0, 0],
    "Factor": "1/0.45359237/9.80665",
    "Symbol": "lb"}

Note also that arithmetical expressions in "Factor" are eval'd to allow for the most accurate input of factors; to prevent a security risk, "Factor" is regex'd to ensure that only numbers and arithmetic symbols are in "Factor" and not any alphabetic characters (see Environment._load_environment in source code to validate).

REPLs and Jupyter Notebook/Lab

forallpeople prioritizes usage conventions over Python conventions. Specifically, the library deliberately switches the intentions behind the __repr__() and __str__() methods: __repr__() will give the pretty printed version and __str__() will return the same. As such, it becomes intuitive to use within any Python REPL and it really shines when used in a Jupyter Notebook, where HTML representations are the default. This also makes it nicer to use with Python libraries such as pandas and numpy.

Using Physicals with Numpy

Physical instances can be used with many numpy operations. See below example:

>>> a = 5 * si.kN
>>> b = 3.5 * si.kN
>>> c = 7.7 * si.kN
>>> d = 6.6 * si.kN
>>> m1 = np.matrix([[a, b], [b, a]])
>>> m2 = np.matrix([[c, d], [d, c]])
>>> m1
matrix([
[5.000 kN, 3.500 kN],
[3.500 kN, 5.000 kN]], dtype=object)
>>> m2
matrix([
[7.700 kN, 6.600 kN],
[6.600 kN, 7.700 kN]], dtype=object)
>>> m1 + m2
matrix([
[12.700 kN, 10.100 kN],
[10.100 kN, 12.700 kN]], dtype=object)
>>> m1 @ m2
matrix([
[61.600 kN², 59.950 kN²],
[59.950 kN², 61.600 kN²]], dtype=object)
>>> m2 - m1
matrix([
[2.700 kN, 3.100 kN],
[3.100 kN, 2.700 kN]], dtype=object)
>>> m1 / m2
matrix([
[0.6493506493506493, 0.5303030303030303],
[0.5303030303030303, 0.6493506493506493]], dtype=object)

forallpeople's People

Contributors

ccaprani avatar connorferster avatar gxyd avatar jonathanp-beca avatar kwinkunks avatar mattonly avatar phil-vivant avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

forallpeople's Issues

Hello and Thank You

Just discovered this package and it looks very interesting ! I am working on a similar package of my own (physipy), and after reading your readme, I'm amazed how our implementations are similar.
I would love to be able to discuss with you about some implementation choices. Many thanks ! Cheers

Problem with rendering \Omega for SI-unit Ohm

Hey everbody! :-)

I am trying to use Jupyter-Notebooks for creating engineering calculation sheets.
Therefore i found handcalcs and forallpeople packages for SI-uinit support.

Works quite nice, but I have trouble with rendering \Omega for Ohm:
Screenshot from 2021-01-30 17-32-38

Whats the matter here? Any Ideas?

Cheers!

Changing unit default display hierarchy (from SI to US customary)

Hi,
I think this is related to this question:
#46 (comment)

But I thought I would open a new request as it is slightly different.

I have been trying to figure out, is it possible to change the hierarchy of unit display within our environment?
Say I am using structural at the top level, is it possible to make length outputs prefer display in inches rather than meters?
I mean, without writing length.to("inch") every time.

If I do all calculations strictly in inches, I have no problem, but once I start mixing in with SI units, like thermal expansion with degrees Kelvin, even if the other units cancel out, my length will suddenly be reported in meters.

Thanks so much Conor!!
Abe

Force different unit system for output

Is there a way to change the units that are returned from an equation? I am using the structural environment and have an equation where all of the parameters are in us imperial units (ksi, psi, inch) but the equation returns units of mm. The library is returning the correct dimension (length) but is no longer following the imperial system. Is there an easy way to change the output system?

image

Rounding function

Hi, thanks for the great work.
The rounding function does not work or maybe I do not understand it.
I calculated a value and the result is 3.347 m (3 decimals).
I would like it displayed with 1 or 2 decimals. Could you include that please?

Issue with my own defined units

Hi, I really like handcalcs + forallpeople so far and I want to use it for master thesis calculations. Unfortunately, there is no support for thermodynamic calculations so I'm working on JSON file that have these units defined.
The issue is when I try to specify output units, it doesn't work.
I use my own environment (link here: https://textbin.cc/QfQSGFtRnb). It doesn't matter what I do, the result is still in mW/m/K and I can't force it make a result in W/m/K.
Can you help me with this issue, please?
xx2

PS: conductivity('CO2', 293.15, 1.01325) is my function that takes result from CoolProp module and make the syntax a little bit easier.
xx

Imported modules and settings
xx3

Changing auto-prefixed values at the end of a sum

Hi there, I am a structural engineer with plans to start using 'handcalcs' and 'forallpeople' more than my standard excel calc pad type stuff. I am very impressed with how it all works but I am struggling to understand if it is possible to change the unit of a result after calculation? The help files are great but i definitely have a (large) gap in my understanding.

For example I may have a kNm moment over a mm3 section modulus, and the output is in kPa, however ideally i would like to display this in MPa. I have read through the help files and it talks about auto-prefixing and the use of a 'to' function?
I am using the structural environment.

%%render 3
F_Ed = (1214N)
lever = (1000
mm)
M_Ed = F_Ed*lever

%%render
sigma_ULS = M_Ed/W_el

The sigma_ULS is displayed in kPa, but ideally i would like to display it in a different unit.

I then tried all different permutations of the line below, to no avail:

sigma_ULS.to(sigma_ULS, unit_name: str = "")

I am really confused by how to use the methods? I have tried to find help online but since I am only just getting started with Python, i find asking the right question pretty difficult in itself!

Thanks in advance,
Tom

Issues with ksi unit

There appears to be an issue with the ksi unit.
Fy = 50ksi, prints 50000 psi
Fy = (50
ksi).to("ksi"), prints 344.738 MPa
Is there a way to get it to print 50 ksi?

Error in definition of tesla unit

Hi, first of all, thanks for developing forallpeople + handcalcs. It is years that I am looking for a replacement of Mathcad and Jupyter + forallpeople + handcalcs seems to finally fit the bill.

There is an error in the definition of tesla

T.repr returns

'Physical(value=1, dimensions=Dimensions(kg=1, m=-2, s=-1, A=0, cd=0, K=0, mol=0), factor=1, precision=3, _prefixed=)'

This is not correct because tesla in SI is defined as

'Physical(value=1, dimensions=Dimensions(kg=1, m=0, s=-2, A=-1, cd=0, K=0, mol=0), factor=1, precision=3, _prefixed=)'

Obtaining the value in a specific unit/factor

Could we document how one can obtain a value using "var.value" but in the same way we specified it? ex:

x=25*mm
x.value
----> 0.25

In this example I would like the value to be in the right units and not the converted SI unit (25, not 0.25) and not have to reconvert *1000

Syntax problem with mathrm for rendering millimeters in forallpeople-2.6.0 and 2.6.1

Dear Connor,

I have been using handcalcs with forallpeople extensively for creating teaching material for a Unit Operations course that I am giving. It has been incredibly helpful.

Unfortunately, with the last release the millimeters are not rendering properly. With forallpeople-2.6.0,

The following code

%%render
d_i = 19.05e-3 * m 
d_o = 20.05e-3 * m
N_t = 56 # El flujo se divide entre 326 tubos
a_t = N_t * (pi * d_i**2/4) # 0.2 p
G_t = F_h/a_t
Re_D = G_t*d_i/(mu_h) # 0.1 p

Produces the following output

image

We can see that an extra 'm' is appended after \mathrm, producing \mathrmm and hence providing a broken output.

This does not happen with forallpeople-2.5.0, and that's what I am using at the moment.

I have the intuition that the problem may be in the file physical_helper_functions.py

Best wishes
Felipe

Simple way to import all existing units in environment

Now the default library only adds a handful of units. When solving/writing phisics equations, a lot of units are needed. Is there a way to import all existing units in the environment like this?

import forallpeople as si

si.environment('all', [top_level=False])

New handcalcs release with forallpeople doesn't render Latex

#%%
import handcalcs.render
import forallpeople as si
si.environment('default')
from importlib.metadata import version
print(version('handcalcs'))

#%%
%%render
length = 4 * si.m
width = 3.5 * si.m
area = length * width
area

image

The new handcalcs release with forallpeople doesn't render the Latex notation. I'm I doing something wrong?

import forallpeople as si loads units to global builtins, not to si as expected from the docs

I have noticed that on version 1.3.1, the import and usage strategy proposed in the readme does not work as expected or is broken. The units end up living in the global namespace regardless of the import statement. See the ipython session below:

Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import forallpeople as si

In [2]: si.m
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-0b9dacaea368> in <module>
----> 1 si.m

AttributeError: module 'forallpeople' has no attribute 'm'

In [3]: m
Out[3]: 1.000 m

In [4]: dir(__builtins__)
Out[4]:
['A',
 'ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BaseException',
# rest omitted


In [5]: si.__version__
Out[5]: '1.3.1'

Is this a bug or supposed to be a feature. I would rather like to be able to choose between the option of importing the units (Physical instances) to the global namespace or preferrably using a separate namespace like demonstrated in the docs.

Inconsistent results when unit is to a power

I'm doing some calculations that might result in inch3 or inch4. The unit inch3 gets converted to mm3, while inch4 remains correct. In other units such at ft, the issue occurs at different powers, such as to the 5th.

Specific calculations:
image

Quick Test:
image

Changing unit

I made a new json which includes

"cm": {
    "Dimension": [0,1,0,0,0,0,0],
    "Value": 0.01},
"mm": {
    "Dimension": [0,1,0,0,0,0,0],
    "Value": 0.001},

now i have a calculation of m * cm * mm and it's result is .
when i leave out the m i get a result of mm².

On what basis is the resulting unit calculated.

And how can i change it? (i want cm in both lines)

Units not cancelling

In the following, the result for c should be unitless:

from handcalcs import render, handcalc
import forallpeople
forallpeople.environment('structural',top_level = True)

%%render
a = 300*kN
b = 20*kN
c = a/sqrt(b**2)

but instead has units "kN".

Unfortunately, I don't have time now to get into a possible solution, but just recording it for now.

Units removed after using sqrt from math module in handcalcs

When placing an equation within a sqrt function from the math module, the units are not carried over correctly. Not sure if this needs to be fixed in the forallpeople codebase instead.

How to reproduce

from math import sqrt

@handcalc(override="long")
def flange_yield(f_star_s_fl_mid,f_star_vf):
    f_star_comb = sqrt(f_star_s_fl_mid**2 + 3 * f_star_vf**2)
    return f_star_comb

flange_yield(...)

image

Expected Behaviour
It should behave the same as using an exponent of 0.5, except with the square root notation. The above equation works fine if you use **0.5

f_star_comb = (f_star_s_fl_mid**2 + 3 * f_star_vf**2)**0.5

image

Unit output ft * psi = MN instead of lbs

I think this is similar to #51

When multiplying two values in us-customary units (using the "structural" environment), I get a SI unit output (N) when it should be lbs. I can convert the units with the .to() method as a workaround, but would like to avoid that. Is there something I can change in the json to prevent this?

Screenshot 2022-06-09 123708

Workaround with unit conversion:
Screenshot 2022-06-09 123935

Latex prefix syntax issue for all base units

When rendering a value in LaTeX using any of the base units (s, A, mol etc.) the prefix is inserted in the wrong position in the LaTeX string. For example, for a value of 5,000 kA, LaTeX would be generated as \mathrkm{A} when expected output would be \mathrm{kA}.

I have checked for a number of different units and it appears to only happen with base units. Derived units seem unaffected.

[Feature request] Make quantity objects json serializable and deserializable

It would be great if SI quantities can be serialized and deserialized from a json string

Current behavior

import forallpeople as si
import json

>>> var = 1 * si.kg
>>> json.dumps(var)

Expected result

Value should be both json serializable and deserialisable

Actual result

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/homebrew/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/opt/homebrew/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/opt/homebrew/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/opt/homebrew/Cellar/[email protected]/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Physical is not JSON serializable

Operating system

Mac OS X

Factors

Hey! Great repo. It will help me a lot, I'm sure of that.

I got one question. Why does this factor values keep showing every time I call a variable?

forall

User defined Default Unit

Hi. First of all thanks a lot for all the good works you have done. While using forallpeople, I need the output of force in one specific unit i.e kN. But the auto-prefixing feature doesn't allow that. Like you have already mentioned it rounds up to the nearest 3rd power of ten.
1
2

I found one .to method. But it also does not seem to work well. Or may be I am wrong somewhere. But I tried.
3
Moreover, even if the .to method works, there will be a lot of extra work to write one more line code after each of the force variables. So, I would like to know if there is any way to work around the problem.

Conda packaging

Hi @connorferster !

Currently forallpeople (and handcalcs) are only available via pip. Do you aim to distribute your packages as conda packages as well?

Kind regards,

Jim

Replace remaining stubs in test_forallpeople.py with proper tests

There are some unit tests in test_forallpeople.py that are still sitting with stubs that are written as return True (bad on me!) Almost all of the functions in the module have tests but there are functions that have tests that are just stubs. Unit tests need to replace the stubs.

.si() method is broken and causing errors

The .si() method is causing an error where it says something like, Physical instance has no attribute ._precision or something. It might be something as simple as a typo in the __init__() method or an inconsistency with naming somewhere else in the code.

The method's behaviour is simply to return a new Physical instance with self.factor set to 1 but with all other attributes set during __init__() (.value, ._precision, .dimensions, etc.) all set to the same value as self.

Choice hierarchy between same base units and .to() method not working

Hi Connor,
I am working with structural fire safety verifications, and I had to add some thermal units to the structural environment.
They were J, MJ, J/m² and W. I am pasting the code additions to the JSON file below to double-check if anything is wrong with it.

"J": {
        "Dimension": [1,2,-2,0,0,0,0],
        "Symbol": "J"},
    "MJ": {
        "Dimension": [1,2,-2,0,0,0,0],
        "Value": 1e6},
    "J_m2": {
        "Dimension": [1,0,-2,0,0,0,0],
        "Symbol": "J*m⁻²"},
    "W": {
        "Dimension": [1,2,-3,0,0,0,0]},

The funny thing is that kN/m and kJ/m² are the same base units (kg/s²), and for some reason, forallpeople is choosing the latter to represent my loads. I don't know how forallpeople choose between two units with the same base. What is the hierarchy, and how can I change that?
I tried to use the .to() method to change to Newtons ('N' or 'N_m'), but it didn't work. It keeps changing to kJ/m², as seen in the screenshot below.
image

Additional line magics to control the units environment

In forallpeople, the units "environment" is controlled by the singleton Environment class that loads a JSON file from the module directory. When a JSON environment is loaded, the units defined in the file are loaded as variables into the namespace. Users can create their own JSON files to create customized units environments.

However, it would be really nice if users were able to dynamically create their own JSON environment files from IPython line magics.

e.g.
%create_env 'name' could create a .json file in the module directory with the filename, 'name'.
%add_to_env 'name' <instance> could add the properties of the Physical instance to the .json file named.
`%remove_from_env 'name' ' could remove the unit from the .json file.

Consider changing the name of the current '%env' line magic

Currently, the %env line magic is the only line magic defined in the module and its purpose is to load/change the units environment. It loads a predefined .json "environments" file in the module directory and instantiates the units defined therein into the user's IPython namespace. Calling it again with a different file will remove the previous set of unit variables from the namespace and load in the new ones. These new variables can be reviewed in IPython with the built-in %who magic.

Should this line magic be called %env? In Python, we typically refer to a virtual environment as an env. Maybe the function should be called something like one of the following:

  • load
  • load_env
  • load_units
  • units_env
  • si
  • si_env

Not sure yet.

Error when multiplying arrays

Hey, I recently got this error when attempting to multiply a numpy array by a scalar "forallpeople.Physical" object
It seems like I can multiply two numpy arrays but not a scalar and array.

This might be expected behavior, if it's not I thought I would mention it:

image

image

Vector multiplication works:
image

Unresolved reference when using top_level

I want to use the units in the top level. The mm doesn't work and PyCharm says m and mm are an unresolved reference.

What do I need to do different?

#%%
import handcalcs.render
import forallpeople as si
si.environment('default', top_level=True)

#%%
si.environment()

#%%
%%render
length = 4 * m
width = 3.5 * mm
area = length * width
area

image

Make a true "machine readable" repr that can be used to instantiate Physicals

forallpeople uses the __repr__() method as its primary method for displaying human readable representations of the Physical instance. This goes against general Python conventions where repr(instance) should return a machine readable representation. Physical instances have implemented a .repr() method to substitute this behaviour. Ideally, this machine readable representation should be able to eval'd to create a new instance.

Currently, a call to .repr() on an instance (in this case, on kN) looks like this:

Physical(value=1000, dimensions=Dimensions(kg=1, m=1, s=-2, A=0, cd=0, K=0, mol=0), factor=1, precision=3, _prefixed=)

This representation could be used to instantiate a new Physical if it were not for the dimensions component which returns a repr for the Dimensions object which it is filled with, a NamedTuple. Dimensions is imported into the namespace but cannot be instantiated as Dimensions(...) without being prefixed with whatever the user has selected as module handle when they import, e.g. import forallpeople as si, the handle is si and to instantiate the Physical from the .repr() result, the user would have to alter the repr to the following:

Physical(value=1000, dimensions=si.Dimensions(kg=1, m=1, s=-2, A=0, cd=0, K=0, mol=0), factor=1, precision=3, _prefixed=)

Not sure what the best approach is here but it would be nice if the .repr() result was able to be eval'd so that this would just work.

"m" is not a known member of module - Type Hinting of dynamically set units

The units imported imported into builtins appear not to be recognised by mypy language server (used in Pylance extension in VS Code) and gives errors depending on the strictness of your type checking.

Given the dynamic setting of builtin functions from the json file, this may not be possible to fix, but thought maybe there is some fixes you have in mind.

At the moment I'm using

2 * u.m # type: ignore

but that is line by line so not very elegant. There is some discussion of using a __setattr__ dunder method on the below link that allows any inputs, but I don't quite understand if that would block any type hints whatsoever for the class its applied to for other methods like .environment() or .to(), which might defeat the whole purpose of having type hints. https://stackoverflow.com/questions/50889677/remove-error-from-mypy-for-attributes-set-dynamically-in-a-python-class

It appears the below inside environments.py is where the __setattr__ occurs depending on the physical_class inputted e.g. u.environment("structural") the units are read from the json files and pushed to builtins.

 # Then the push
        for var_name, physical in units_dict.items():
            setattr(builtins, var_name, physical)

Because the code isn't evaluated until runtime, then Pylance would not know about what variables will be created.

How to reproduce

  • Use Pylance language server for VS code
  • import forallpeople
import forallpeople as u
u.environment('structural')
  • Use any units such as m or kN
2 * u.m
  • Following problem occurs on type checker:
    "m" is not a known member of module

Pickling causes AttributeError

When we try to pickle.loads an si unit an AttributeError is raised.

import forallpeople as si
import pickle

p = pickle.dumps(si.s) # or any other unit from si
pickle.loads(p) # Causes an AttributeError

Is this the intended behaviour?

matplotlib compatibility

Ive noticed that the auto-prefixing functionality means the prefix is ignored when plotting with matplotlib, for example if I have a value going from 1 Hz to 10kHz, it will "reset" at 1kHz since matplotlib is plotting it as a 1, not as 1000. as I result I have to remove all units before I can plot data.

It seems like .value works correctly with autoprefixes, so im not sure where the incompatibility lies

Error using complex numbers

If I try to add units to a complex value, ForAllPeople throws an error

For example if I had a purely reactive current:

import forallpeople

forallpeople.environment('default', top_level=True)

1j * A

This throws an error:
TypeError: '<' not supported between instances of 'int' and 'complex'

Error using .to() converting ft to meters

Works fine converting from m to ft but I get this warning going the other way:

/.../forallpeople/init.py:164: UserWarning: No unit defined for 'm' on 1.000 ft.
if not unit_match: warnings.warn(f"No unit defined for '{unit_name}' on {self}.")
304.800 mm

Comparison of unit values

It looks like there might be a bug in the comparison

>>> 1e-6 * m ==  0 * m
False
>>> 1e-6 * m >  0 * m
True
>>> 1e-9 * m ==  0 * m
True
>>> 1e-9 * m > 0 * m
False

Location of environment .json files.

When installing forallpeople, the environment .json files are stored in the main repository of the package. I'm looking to install forallpeople and add a custom environment to my built package. This is currently quite difficult as my package needs to look up where forallpeople is installed and add the custom environment to the installed package.

I have two suggestions to overcome this issue;

  1. Store the .json files in conda environment \share folder (as jupyter does with its templates). Other packages can add .json files to the share folder.
  2. add a function in forallpeople where you can import .json files from other locations. Maybe this is already there, and I just didn't find it. If it's already there you can close this issue :)

I'm happy to help store the .json files in the share folder.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.