ucon 0.5.0__py3-none-any.whl → 0.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ucon/graph.py CHANGED
@@ -28,7 +28,15 @@ from contextvars import ContextVar
28
28
  from dataclasses import dataclass, field
29
29
  from typing import Union
30
30
 
31
- from ucon.core import Dimension, Unit, UnitFactor, UnitProduct, Scale
31
+ from ucon.core import (
32
+ BasisTransform,
33
+ Dimension,
34
+ RebasedUnit,
35
+ Unit,
36
+ UnitFactor,
37
+ UnitProduct,
38
+ Scale,
39
+ )
32
40
  from ucon.maps import Map, LinearMap, AffineMap
33
41
 
34
42
 
@@ -66,6 +74,9 @@ class ConversionGraph:
66
74
  # Edges between UnitProducts (keyed by frozen factor representation)
67
75
  _product_edges: dict[tuple, dict[tuple, Map]] = field(default_factory=dict)
68
76
 
77
+ # Rebased units: original unit → RebasedUnit (for cross-basis edges)
78
+ _rebased: dict[Unit, RebasedUnit] = field(default_factory=dict)
79
+
69
80
  # ------------- Edge Management -------------------------------------------
70
81
 
71
82
  def add_edge(
@@ -74,6 +85,7 @@ class ConversionGraph:
74
85
  src: Union[Unit, UnitProduct],
75
86
  dst: Union[Unit, UnitProduct],
76
87
  map: Map,
88
+ basis_transform: BasisTransform | None = None,
77
89
  ) -> None:
78
90
  """Register a conversion edge. Also registers the inverse.
79
91
 
@@ -85,15 +97,31 @@ class ConversionGraph:
85
97
  Destination unit expression.
86
98
  map : Map
87
99
  The conversion morphism (src → dst).
100
+ basis_transform : BasisTransform, optional
101
+ If provided, creates a cross-basis edge. The src unit is rebased
102
+ to the dst's dimension and the edge connects the rebased unit
103
+ to dst.
88
104
 
89
105
  Raises
90
106
  ------
91
107
  DimensionMismatch
92
- If src and dst have different dimensions.
108
+ If src and dst have different dimensions (and no basis_transform).
93
109
  CyclicInconsistency
94
110
  If the reverse edge exists and round-trip is not identity.
95
111
  """
96
- # Handle Unit vs UnitProduct dispatch
112
+ # Cross-basis edge with BasisTransform
113
+ if basis_transform is not None:
114
+ if isinstance(src, Unit) and not isinstance(src, UnitProduct):
115
+ if isinstance(dst, Unit) and not isinstance(dst, UnitProduct):
116
+ self._add_cross_basis_edge(
117
+ src=src,
118
+ dst=dst,
119
+ map=map,
120
+ basis_transform=basis_transform,
121
+ )
122
+ return
123
+
124
+ # Handle Unit vs UnitProduct dispatch (normal case)
97
125
  if isinstance(src, Unit) and not isinstance(src, UnitProduct):
98
126
  if isinstance(dst, Unit) and not isinstance(dst, UnitProduct):
99
127
  self._add_unit_edge(src=src, dst=dst, map=map)
@@ -142,6 +170,114 @@ class ConversionGraph:
142
170
  self._product_edges.setdefault(src_key, {})[dst_key] = map
143
171
  self._product_edges.setdefault(dst_key, {})[src_key] = map.inverse()
144
172
 
173
+ def _add_cross_basis_edge(
174
+ self,
175
+ *,
176
+ src: Unit,
177
+ dst: Unit,
178
+ map: Map,
179
+ basis_transform: BasisTransform,
180
+ ) -> None:
181
+ """Add cross-basis edge between Units via BasisTransform.
182
+
183
+ Creates a RebasedUnit for src in the destination's dimension partition,
184
+ then stores the edge from the rebased unit to dst.
185
+ """
186
+ # Validate that the transform maps src to dst's dimension
187
+ if not basis_transform.validate_edge(src, dst):
188
+ raise DimensionMismatch(
189
+ f"Transform {basis_transform.src.name} -> {basis_transform.dst.name} "
190
+ f"does not map {src.name} to {dst.name}'s dimension"
191
+ )
192
+
193
+ # Create RebasedUnit in destination's dimension partition
194
+ rebased = RebasedUnit(
195
+ original=src,
196
+ rebased_dimension=dst.dimension,
197
+ basis_transform=basis_transform,
198
+ )
199
+ self._rebased[src] = rebased
200
+
201
+ # Store edge from rebased to dst (same dimension now)
202
+ dim = dst.dimension
203
+ self._ensure_dimension(dim)
204
+ self._unit_edges[dim].setdefault(rebased, {})[dst] = map
205
+ self._unit_edges[dim].setdefault(dst, {})[rebased] = map.inverse()
206
+
207
+ def connect_systems(
208
+ self,
209
+ *,
210
+ basis_transform: BasisTransform,
211
+ edges: dict[tuple[Unit, Unit], Map],
212
+ ) -> None:
213
+ """Bulk-add edges between systems.
214
+
215
+ Parameters
216
+ ----------
217
+ basis_transform : BasisTransform
218
+ The transform bridging the two systems.
219
+ edges : dict
220
+ Mapping from (src_unit, dst_unit) to Map.
221
+ """
222
+ for (src, dst), edge_map in edges.items():
223
+ self.add_edge(
224
+ src=src,
225
+ dst=dst,
226
+ map=edge_map,
227
+ basis_transform=basis_transform,
228
+ )
229
+
230
+ def list_rebased_units(self) -> dict[Unit, RebasedUnit]:
231
+ """Return all rebased units in the graph.
232
+
233
+ Returns
234
+ -------
235
+ dict[Unit, RebasedUnit]
236
+ Mapping from original unit to its RebasedUnit.
237
+ """
238
+ return dict(self._rebased)
239
+
240
+ def list_transforms(self) -> list[BasisTransform]:
241
+ """Return all BasisTransforms active in the graph.
242
+
243
+ Returns
244
+ -------
245
+ list[BasisTransform]
246
+ Unique transforms used by rebased units.
247
+ """
248
+ seen = set()
249
+ result = []
250
+ for rebased in self._rebased.values():
251
+ bt = rebased.basis_transform
252
+ if id(bt) not in seen:
253
+ seen.add(id(bt))
254
+ result.append(bt)
255
+ return result
256
+
257
+ def edges_for_transform(self, transform: BasisTransform) -> list[tuple[Unit, Unit]]:
258
+ """Return all edges that use a specific BasisTransform.
259
+
260
+ Parameters
261
+ ----------
262
+ transform : BasisTransform
263
+ The transform to filter by.
264
+
265
+ Returns
266
+ -------
267
+ list[tuple[Unit, Unit]]
268
+ List of (original_unit, destination_unit) pairs.
269
+ """
270
+ result = []
271
+ for original, rebased in self._rebased.items():
272
+ if rebased.basis_transform == transform:
273
+ # Find the destination unit (the one the rebased unit connects to)
274
+ dim = rebased.dimension
275
+ if dim in self._unit_edges and rebased in self._unit_edges[dim]:
276
+ for dst in self._unit_edges[dim][rebased]:
277
+ if not isinstance(dst, RebasedUnit):
278
+ result.append((original, dst))
279
+ return result
280
+
145
281
  def _ensure_dimension(self, dim: Dimension) -> None:
146
282
  if dim not in self._unit_edges:
147
283
  self._unit_edges[dim] = {}
@@ -206,10 +342,28 @@ class ConversionGraph:
206
342
  return self._convert_products(src=src_prod, dst=dst_prod)
207
343
 
208
344
  def _convert_units(self, *, src: Unit, dst: Unit) -> Map:
209
- """Convert between plain Units via BFS."""
345
+ """Convert between plain Units via BFS.
346
+
347
+ Handles cross-basis conversions via rebased units.
348
+ """
210
349
  if src == dst:
211
350
  return LinearMap.identity()
212
351
 
352
+ # Check if src has a rebased version that can reach dst
353
+ if src in self._rebased:
354
+ rebased = self._rebased[src]
355
+ if rebased.dimension == dst.dimension:
356
+ # Convert via the rebased unit
357
+ return self._bfs_convert(start=rebased, target=dst, dim=dst.dimension)
358
+
359
+ # Check if dst has a rebased version (inverse conversion)
360
+ if dst in self._rebased:
361
+ rebased_dst = self._rebased[dst]
362
+ if rebased_dst.dimension == src.dimension:
363
+ # Convert from src to the rebased dst
364
+ return self._bfs_convert(start=src, target=rebased_dst, dim=src.dimension)
365
+
366
+ # Check for dimension mismatch
213
367
  if src.dimension != dst.dimension:
214
368
  raise DimensionMismatch(f"{src.dimension} != {dst.dimension}")
215
369
 
@@ -217,13 +371,16 @@ class ConversionGraph:
217
371
  if self._has_direct_unit_edge(src=src, dst=dst):
218
372
  return self._get_direct_unit_edge(src=src, dst=dst)
219
373
 
220
- # BFS
221
- dim = src.dimension
374
+ # BFS in same dimension
375
+ return self._bfs_convert(start=src, target=dst, dim=src.dimension)
376
+
377
+ def _bfs_convert(self, *, start, target, dim: Dimension) -> Map:
378
+ """BFS to find conversion path within a dimension."""
222
379
  if dim not in self._unit_edges:
223
380
  raise ConversionNotFound(f"No edges for dimension {dim}")
224
381
 
225
- visited: dict[Unit, Map] = {src: LinearMap.identity()}
226
- queue = deque([src])
382
+ visited: dict = {start: LinearMap.identity()}
383
+ queue = deque([start])
227
384
 
228
385
  while queue:
229
386
  current = queue.popleft()
@@ -239,12 +396,12 @@ class ConversionGraph:
239
396
  composed = edge_map @ current_map
240
397
  visited[neighbor] = composed
241
398
 
242
- if neighbor == dst:
399
+ if neighbor == target:
243
400
  return composed
244
401
 
245
402
  queue.append(neighbor)
246
403
 
247
- raise ConversionNotFound(f"No path from {src} to {dst}")
404
+ raise ConversionNotFound(f"No path from {start} to {target}")
248
405
 
249
406
  def _convert_products(self, *, src: UnitProduct, dst: UnitProduct) -> Map:
250
407
  """Convert between UnitProducts.
ucon/maps.py CHANGED
@@ -25,7 +25,7 @@ from dataclasses import dataclass
25
25
  class Map(ABC):
26
26
  """Abstract base for all conversion morphisms.
27
27
 
28
- Subclasses must implement ``__call__``, ``inverse``, and ``__pow__``.
28
+ Subclasses must implement ``__call__``, ``inverse``, ``__pow__``, and ``derivative``.
29
29
  Composition via ``@`` defaults to :class:`ComposedMap`; subclasses may
30
30
  override for closed composition within their own type.
31
31
  """
@@ -50,6 +50,14 @@ class Map(ABC):
50
50
  """Raise map to a power (for exponent handling in factorwise conversion)."""
51
51
  ...
52
52
 
53
+ @abstractmethod
54
+ def derivative(self, x: float) -> float:
55
+ """Return the derivative of the map at point x.
56
+
57
+ Used for uncertainty propagation: δy = |f'(x)| * δx
58
+ """
59
+ ...
60
+
53
61
  def is_identity(self, tol: float = 1e-9) -> bool:
54
62
  """Check if this map is approximately the identity."""
55
63
  return abs(self(1.0) - 1.0) < tol and abs(self(0.0) - 0.0) < tol
@@ -86,6 +94,10 @@ class LinearMap(Map):
86
94
  def __pow__(self, exp: float) -> LinearMap:
87
95
  return LinearMap(self.a ** exp)
88
96
 
97
+ def derivative(self, x: float) -> float:
98
+ """Derivative of y = a*x is a (constant)."""
99
+ return self.a
100
+
89
101
  @classmethod
90
102
  def identity(cls) -> LinearMap:
91
103
  return cls(1.0)
@@ -128,6 +140,10 @@ class AffineMap(Map):
128
140
  return self.inverse()
129
141
  raise ValueError("AffineMap only supports exp=1 or exp=-1")
130
142
 
143
+ def derivative(self, x: float) -> float:
144
+ """Derivative of y = a*x + b is a (constant)."""
145
+ return self.a
146
+
131
147
 
132
148
  @dataclass(frozen=True)
133
149
  class ComposedMap(Map):
@@ -159,3 +175,8 @@ class ComposedMap(Map):
159
175
  if exp == -1:
160
176
  return self.inverse()
161
177
  raise ValueError("ComposedMap only supports exp=1 or exp=-1")
178
+
179
+ def derivative(self, x: float) -> float:
180
+ """Chain rule: d/dx [outer(inner(x))] = outer'(inner(x)) * inner'(x)."""
181
+ inner_val = self.inner(x)
182
+ return self.outer.derivative(inner_val) * self.inner.derivative(x)
ucon/units.py CHANGED
@@ -28,7 +28,7 @@ Notes
28
28
  The design allows for future extensibility: users can register their own units,
29
29
  systems, or aliases dynamically, without modifying the core definitions.
30
30
  """
31
- from ucon.core import Dimension, Unit
31
+ from ucon.core import Dimension, Unit, UnitSystem
32
32
 
33
33
 
34
34
  none = Unit()
@@ -49,6 +49,7 @@ joule_per_kelvin = Unit(name='joule_per_kelvin', dimension=Dimension.entropy, al
49
49
  kelvin = Unit(name='kelvin', dimension=Dimension.temperature, aliases=('K',))
50
50
  kilogram = Unit(name='kilogram', dimension=Dimension.mass, aliases=('kg',))
51
51
  liter = Unit(name='liter', dimension=Dimension.volume, aliases=('L', 'l'))
52
+ candela = Unit(name='candela', dimension=Dimension.luminous_intensity, aliases=('cd',))
52
53
  lumen = Unit(name='lumen', dimension=Dimension.luminous_intensity, aliases=('lm',))
53
54
  lux = Unit(name='lux', dimension=Dimension.illuminance, aliases=('lx',))
54
55
  meter = Unit(name='meter', dimension=Dimension.length, aliases=('m',))
@@ -141,6 +142,32 @@ basis_point = Unit(name='basis_point', dimension=Dimension.ratio, aliases=('bp',
141
142
  webers = weber
142
143
 
143
144
 
145
+ # -- Predefined Unit Systems -----------------------------------------------
146
+ si = UnitSystem(
147
+ name="SI",
148
+ bases={
149
+ Dimension.length: meter,
150
+ Dimension.mass: kilogram,
151
+ Dimension.time: second,
152
+ Dimension.temperature: kelvin,
153
+ Dimension.current: ampere,
154
+ Dimension.amount_of_substance: mole,
155
+ Dimension.luminous_intensity: candela,
156
+ }
157
+ )
158
+
159
+ imperial = UnitSystem(
160
+ name="Imperial",
161
+ bases={
162
+ Dimension.length: foot,
163
+ Dimension.mass: pound,
164
+ Dimension.time: second,
165
+ Dimension.temperature: fahrenheit,
166
+ }
167
+ )
168
+ # --------------------------------------------------------------------------
169
+
170
+
144
171
  def have(name: str) -> bool:
145
172
  assert name, "Must provide a unit name to check"
146
173
  assert isinstance(name, str), "Unit name must be a string"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ucon
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: a tool for dimensional analysis: a "Unit CONverter"
5
5
  Home-page: https://github.com/withtwoemms/ucon
6
6
  Author: Emmanuel I. Obi
@@ -67,6 +67,7 @@ It combines **units**, **scales**, and **dimensions** into a composable algebra
67
67
  - Scale-aware arithmetic via `UnitFactor` and `UnitProduct`
68
68
  - Metric and binary prefixes (`kilo`, `kibi`, `micro`, `mebi`, etc.)
69
69
  - Pseudo-dimensions for angles, solid angles, and ratios with semantic isolation
70
+ - Uncertainty propagation through arithmetic and conversions
70
71
  - A clean foundation for physics, chemistry, data modeling, and beyond
71
72
 
72
73
  Think of it as **`decimal.Decimal` for the physical world** — precise, predictable, and type-safe.
@@ -91,6 +92,9 @@ To best answer this question, we turn to an age-old technique ([dimensional anal
91
92
  | **`Ratio`** | `ucon.core` | Represents the division of two `Number` objects; captures relationships between quantities. | Expressing rates, densities, efficiencies (e.g., energy / time = power, length / time = velocity). |
92
93
  | **`Map`** hierarchy | `ucon.maps` | Composable conversion morphisms: `LinearMap`, `AffineMap`, `ComposedMap`. | Defining conversion functions between units (e.g., meter→foot, celsius→kelvin). |
93
94
  | **`ConversionGraph`** | `ucon.graph` | Registry of unit conversion edges with BFS path composition. | Converting between units via `Number.to(target)`; managing default and custom graphs. |
95
+ | **`UnitSystem`** | `ucon.core` | Named mapping from dimensions to base units (e.g., SI, Imperial). | Defining coherent unit systems; grouping base units by dimension. |
96
+ | **`BasisTransform`** | `ucon.core` | Matrix-based transformation between dimensional exponent spaces. | Converting between incompatible dimensional structures; exact arithmetic with `Fraction`. |
97
+ | **`RebasedUnit`** | `ucon.core` | A unit rebased to another system's dimension, preserving provenance. | Cross-basis conversions; tracking original unit through basis changes. |
94
98
  | **`units` module** | `ucon.units` | Defines canonical unit instances (SI, imperial, information, and derived units). | Quick access to standard physical units (`units.meter`, `units.foot`, `units.byte`, etc.). |
95
99
 
96
100
  ### Under the Hood
@@ -215,6 +219,102 @@ print(ratio.to(units.ppm)) # <500000.0 ppm>
215
219
  units.radian(1).to(units.percent) # raises ConversionNotFound
216
220
  ```
217
221
 
222
+ Uncertainty propagates through arithmetic and conversions:
223
+ ```python
224
+ from ucon import units
225
+
226
+ # Measurements with uncertainty
227
+ length = units.meter(1.234, uncertainty=0.005)
228
+ width = units.meter(0.567, uncertainty=0.003)
229
+
230
+ print(length) # <1.234 ± 0.005 m>
231
+
232
+ # Uncertainty propagates through arithmetic (quadrature)
233
+ area = length * width
234
+ print(area) # <0.699678 ± 0.00424... m²>
235
+
236
+ # Uncertainty propagates through conversion
237
+ length_ft = length.to(units.foot)
238
+ print(length_ft) # <4.048... ± 0.0164... ft>
239
+ ```
240
+
241
+ Unit systems and basis transforms enable conversions between incompatible dimensional structures.
242
+ This goes beyond simple unit conversion (meter → foot) into structural transformation:
243
+
244
+ ```python
245
+ from fractions import Fraction
246
+ from ucon import BasisTransform, Dimension, Unit, UnitSystem, units
247
+ from ucon.graph import ConversionGraph
248
+ from ucon.maps import LinearMap
249
+
250
+ # The realm of Valdris has three fundamental dimensions:
251
+ # - Aether (A): magical energy substrate
252
+ # - Resonance (R): vibrational frequency of magic
253
+ # - Substance (S): physical matter
254
+ #
255
+ # These combine into SI dimensions via a transformation matrix:
256
+ #
257
+ # | L | | 2 0 0 | | A |
258
+ # | M | = | 1 0 1 | × | R |
259
+ # | T | |-2 -1 0 | | S |
260
+ #
261
+ # Reading the columns:
262
+ # - 1 aether contributes: L², M, T⁻² (energy-like)
263
+ # - 1 resonance contributes: T⁻¹ (frequency-like)
264
+ # - 1 substance contributes: M (mass-like)
265
+
266
+ # Fantasy base units
267
+ mote = Unit(name='mote', dimension=Dimension.energy, aliases=('mt',))
268
+ chime = Unit(name='chime', dimension=Dimension.frequency, aliases=('ch',))
269
+ ite = Unit(name='ite', dimension=Dimension.mass, aliases=('it',))
270
+
271
+ valdris = UnitSystem(
272
+ name="Valdris",
273
+ bases={
274
+ Dimension.energy: mote,
275
+ Dimension.frequency: chime,
276
+ Dimension.mass: ite,
277
+ }
278
+ )
279
+
280
+ # The basis transform encodes how Valdris dimensions compose into SI
281
+ valdris_to_si = BasisTransform(
282
+ src=valdris,
283
+ dst=units.si,
284
+ src_dimensions=(Dimension.energy, Dimension.frequency, Dimension.mass),
285
+ dst_dimensions=(Dimension.energy, Dimension.frequency, Dimension.mass),
286
+ matrix=(
287
+ (2, 0, 0), # energy: 2 × aether
288
+ (1, 0, 1), # frequency: aether + substance
289
+ (-2, -1, 0), # mass: -2×aether - resonance
290
+ ),
291
+ )
292
+
293
+ # Physical calibration: how many SI units per fantasy unit
294
+ graph = ConversionGraph()
295
+ graph.connect_systems(
296
+ basis_transform=valdris_to_si,
297
+ edges={
298
+ (mote, units.joule): LinearMap(42), # 1 mote = 42 J
299
+ (chime, units.hertz): LinearMap(7), # 1 chime = 7 Hz
300
+ (ite, units.kilogram): LinearMap(Fraction(1, 2)), # 1 ite = 0.5 kg
301
+ }
302
+ )
303
+
304
+ # Game engine converts between physics systems
305
+ energy_map = graph.convert(src=mote, dst=units.joule)
306
+ energy_map(10) # 420 joules from 10 motes
307
+
308
+ # Inverse: display real-world values in game units
309
+ joule_to_mote = graph.convert(src=units.joule, dst=mote)
310
+ joule_to_mote(420) # 10 motes
311
+
312
+ # The transform is invertible with exact Fraction arithmetic
313
+ valdris_to_si.is_invertible # True
314
+ ```
315
+
316
+ This enables fantasy game physics, or any field where the dimensional structure differs from SI.
317
+
218
318
  ---
219
319
 
220
320
  ## Roadmap Highlights
@@ -224,8 +324,10 @@ units.radian(1).to(units.percent) # raises ConversionNotFound
224
324
  | **0.3.x** | Dimensional Algebra | Unit/Scale separation, `UnitFactor`, `UnitProduct` | ✅ Complete |
225
325
  | **0.4.x** | Conversion System | `ConversionGraph`, `Number.to()`, callable units | ✅ Complete |
226
326
  | **0.5.0** | Dimensionless Units | Pseudo-dimensions for angle, solid angle, ratio | ✅ Complete |
227
- | **0.5.x** | Metrology | Uncertainty propagation, `UnitSystem` | 🚧 In Progress |
228
- | **0.7.x** | Pydantic Integration | Type-safe quantity validation | Planned |
327
+ | **0.5.x** | Uncertainty | Propagation through arithmetic and conversions | Complete |
328
+ | **0.5.x** | Unit Systems | `BasisTransform`, `UnitSystem`, cross-basis conversion | Complete |
329
+ | **0.6.x** | Pydantic Integration | Type-safe quantity validation | ⏳ Planned |
330
+ | **0.7.x** | NumPy Arrays | Vectorized conversion and arithmetic | ⏳ Planned |
229
331
 
230
332
  See full roadmap: [ROADMAP.md](./ROADMAP.md)
231
333
 
@@ -0,0 +1,29 @@
1
+ tests/ucon/__init__.py,sha256=9BAHYTs27Ed3VgqiMUH4XtVttAmOPgK0Zvj-dUNo7D8,119
2
+ tests/ucon/test_algebra.py,sha256=NnoQOSMW8NJlOTnbr3M_5epvnDPXpVTgO21L2LcytRY,8503
3
+ tests/ucon/test_basis_transform.py,sha256=P7ccr9wgDPCwCmsj4dceu3V0A72qFbTavjzm9kB3xP8,16710
4
+ tests/ucon/test_core.py,sha256=bmwSRWPlhwossy5NJ9rcPWujFmzBBPOeZzPAzN1acFg,32631
5
+ tests/ucon/test_default_graph_conversions.py,sha256=rkcDcSV1_kZeuPf4ModHDpgfkOPZS32xcKq7KPDRN-0,15760
6
+ tests/ucon/test_dimensionless_units.py,sha256=K6BrIPOFL9IO_ksR8t_oJUXmjTgqBUzMdgaV-hZc52w,8410
7
+ tests/ucon/test_graph_basis_transform.py,sha256=5-WglJaR1N_mJlqR6i8NuxLJ_FASqb5a8WoO_177smU,8249
8
+ tests/ucon/test_quantity.py,sha256=md5nbmy0u2cFBdqNeu-ROhoj29vYrIlGm_AjlmCttgc,24519
9
+ tests/ucon/test_rebased_unit.py,sha256=n2mksEYSJ8UJJXXwlgaLKg3THaf7_LKzWB7kwjoaXEU,5150
10
+ tests/ucon/test_uncertainty.py,sha256=KkJw2dJR0EToxPpBN24x735jr9fv6a2myxjvhOH4MPU,9649
11
+ tests/ucon/test_unit_system.py,sha256=gRc3fMvo9ded1tBUQWLKpcbpBepMAb7gPu8XvFzQZaM,5860
12
+ tests/ucon/test_units.py,sha256=SILymDtDNDyxEhkYQubrfkakKCMexwEwjyHfhrkDrMI,869
13
+ tests/ucon/test_vector_fraction.py,sha256=fTgxlV9aSP15sA4gATTXBzIDbtKXwWqG1Ip0o-V2B4Y,6369
14
+ tests/ucon/conversion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ tests/ucon/conversion/test_graph.py,sha256=fs0aP6qNf8eE1uI7SoGSCW2XAkHYb7T9aaI-kzmO02c,16955
16
+ tests/ucon/conversion/test_map.py,sha256=DVFQ3xwp16Nuy9EtZRjKlWbkXfRUcM1mOzFrS4HhOaw,13886
17
+ ucon/__init__.py,sha256=M_sijIUYPvU87Jtnq_O2X7TS4x9RW1LHZJ8bbm3gfk0,2255
18
+ ucon/algebra.py,sha256=6QrPyD23L93XSrnIORcYEx2CLDv4WDcrh6H_hxeeOus,8668
19
+ ucon/core.py,sha256=j2Xw73-Xuh0CkaUYEv5ljsxjt-XdthiH-EbqUBgG1a8,63139
20
+ ucon/graph.py,sha256=Ec0Q2QiAGUm2RaxrKnpFHtwpNvTf4PYbvo62BWtGJG8,21159
21
+ ucon/maps.py,sha256=tWP4ayYCEazJzf81EP1_fmtADhg18D1eHldudAMEY0U,5460
22
+ ucon/quantity.py,sha256=GBxZ_96nocx-8F-usNWGbPvWHRhRgdZzqfH9Sx69iC4,465
23
+ ucon/units.py,sha256=MWCJhicK6jQb71fREyW_HSfGFKL8KEQej2JnySL_MjE,8285
24
+ ucon-0.5.2.dist-info/licenses/LICENSE,sha256=LtimSYBSw1L_X6n1-VEdZRdwuROzPumrMUNX21asFuI,11356
25
+ ucon-0.5.2.dist-info/licenses/NOTICE,sha256=bh4fBOItio3kM4hSNYhqfFpcaAvOoixjD7Du8im-sYA,1079
26
+ ucon-0.5.2.dist-info/METADATA,sha256=CTRgYp67awjnjy7ZzKL2opd81T2RK1feQ6AXMnXmSj4,17200
27
+ ucon-0.5.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
28
+ ucon-0.5.2.dist-info/top_level.txt,sha256=zZYRJiQrVUtN32ziJD2YEq7ClSvDmVYHYy5ArRAZGxI,11
29
+ ucon-0.5.2.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- tests/ucon/__init__.py,sha256=9BAHYTs27Ed3VgqiMUH4XtVttAmOPgK0Zvj-dUNo7D8,119
2
- tests/ucon/test_algebra.py,sha256=NnoQOSMW8NJlOTnbr3M_5epvnDPXpVTgO21L2LcytRY,8503
3
- tests/ucon/test_core.py,sha256=bmwSRWPlhwossy5NJ9rcPWujFmzBBPOeZzPAzN1acFg,32631
4
- tests/ucon/test_default_graph_conversions.py,sha256=rkcDcSV1_kZeuPf4ModHDpgfkOPZS32xcKq7KPDRN-0,15760
5
- tests/ucon/test_dimensionless_units.py,sha256=K6BrIPOFL9IO_ksR8t_oJUXmjTgqBUzMdgaV-hZc52w,8410
6
- tests/ucon/test_quantity.py,sha256=md5nbmy0u2cFBdqNeu-ROhoj29vYrIlGm_AjlmCttgc,24519
7
- tests/ucon/test_units.py,sha256=SILymDtDNDyxEhkYQubrfkakKCMexwEwjyHfhrkDrMI,869
8
- tests/ucon/conversion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- tests/ucon/conversion/test_graph.py,sha256=fs0aP6qNf8eE1uI7SoGSCW2XAkHYb7T9aaI-kzmO02c,16955
10
- tests/ucon/conversion/test_map.py,sha256=DVFQ3xwp16Nuy9EtZRjKlWbkXfRUcM1mOzFrS4HhOaw,13886
11
- ucon/__init__.py,sha256=jaFcNFZC5gxHKzM8OkS1pLxltp6ToLRVpuuhJQY9FKQ,2000
12
- ucon/algebra.py,sha256=4JiT_SHHep86Sv3tVkgKsRY95lBRASMkyH4vOUA-gfM,7459
13
- ucon/core.py,sha256=GjLKV0ERyYLhBZBpyIfCrKL718EN1RlUKwqxx2B3Rc4,43606
14
- ucon/graph.py,sha256=lPoYSvHNGBZxeZ-4dyZIu2OS5R1JTo0qPZ9wd0vg-s4,15566
15
- ucon/maps.py,sha256=yyZ7RqnohO2joTUvvKh40in7E6SKMQIQ8jkECO0-_NA,4753
16
- ucon/quantity.py,sha256=GBxZ_96nocx-8F-usNWGbPvWHRhRgdZzqfH9Sx69iC4,465
17
- ucon/units.py,sha256=u1ILwGllzNiwGLadlg5jguKPyFV1u-CZSUMgUDWTen4,7509
18
- ucon-0.5.0.dist-info/licenses/LICENSE,sha256=LtimSYBSw1L_X6n1-VEdZRdwuROzPumrMUNX21asFuI,11356
19
- ucon-0.5.0.dist-info/licenses/NOTICE,sha256=bh4fBOItio3kM4hSNYhqfFpcaAvOoixjD7Du8im-sYA,1079
20
- ucon-0.5.0.dist-info/METADATA,sha256=TZDRVKaAMyCbbwanZ44Ej5JsCGNf0iWch1PD_CFnIx4,12901
21
- ucon-0.5.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
22
- ucon-0.5.0.dist-info/top_level.txt,sha256=zZYRJiQrVUtN32ziJD2YEq7ClSvDmVYHYy5ArRAZGxI,11
23
- ucon-0.5.0.dist-info/RECORD,,
File without changes