feat(python): implement python bindings for all components and solvers

This commit is contained in:
Sepehr
2026-02-21 20:34:56 +01:00
parent 8ef8cd2eba
commit 4440132b0a
310 changed files with 11577 additions and 397 deletions

View File

View File

@@ -0,0 +1 @@
"""Pytest configuration for Entropyk Python bindings tests."""

View File

@@ -0,0 +1,98 @@
"""Entropyk — Performance Benchmark Tests.
Tests that measure Python→Rust call overhead and verify performance.
These are not unit tests — they measure timing and should be run with
``pytest -s`` for visible output.
"""
import time
import pytest
import entropyk
class TestConstructorOverhead:
"""Benchmark component construction overhead."""
def test_1000_compressor_constructions(self):
"""Constructing 1000 Compressors should be very fast (< 100 ms)."""
start = time.perf_counter()
for _ in range(1000):
entropyk.Compressor()
elapsed = time.perf_counter() - start
assert elapsed < 0.1, f"1000 Compressor constructions took {elapsed:.3f}s"
def test_1000_pressure_constructions(self):
"""Constructing 1000 Pressure objects should be very fast."""
start = time.perf_counter()
for _ in range(1000):
entropyk.Pressure(bar=1.0)
elapsed = time.perf_counter() - start
assert elapsed < 0.1, f"1000 Pressure constructions took {elapsed:.3f}s"
def test_1000_temperature_constructions(self):
"""Constructing 1000 Temperature objects should be very fast."""
start = time.perf_counter()
for _ in range(1000):
entropyk.Temperature(celsius=25.0)
elapsed = time.perf_counter() - start
assert elapsed < 0.1, f"1000 Temperature constructions took {elapsed:.3f}s"
class TestConversionOverhead:
"""Benchmark unit conversion overhead."""
def test_1000_pressure_conversions(self):
"""Unit conversions should add negligible overhead."""
p = entropyk.Pressure(bar=1.0)
start = time.perf_counter()
for _ in range(1000):
_ = p.to_bar()
_ = p.to_pascals()
_ = p.to_kpa()
elapsed = time.perf_counter() - start
assert elapsed < 0.1, f"3000 pressure conversions took {elapsed:.3f}s"
def test_1000_temperature_conversions(self):
"""Temperature conversions should be fast."""
t = entropyk.Temperature(celsius=25.0)
start = time.perf_counter()
for _ in range(1000):
_ = t.to_celsius()
_ = t.to_kelvin()
_ = t.to_fahrenheit()
elapsed = time.perf_counter() - start
assert elapsed < 0.1, f"3000 temperature conversions took {elapsed:.3f}s"
class TestArithmeticOverhead:
"""Benchmark arithmetic operation overhead."""
def test_1000_additions(self):
"""1000 pressure additions should be fast."""
p1 = entropyk.Pressure(pa=101325.0)
p2 = entropyk.Pressure(pa=50000.0)
start = time.perf_counter()
for _ in range(1000):
_ = p1 + p2
elapsed = time.perf_counter() - start
assert elapsed < 0.1, f"1000 additions took {elapsed:.3f}s"
class TestSystemBuildOverhead:
"""Benchmark system construction overhead."""
def test_100_system_builds(self):
"""Building 100 simple systems (4 components + 4 edges) should be fast."""
start = time.perf_counter()
for _ in range(100):
system = entropyk.System()
c = system.add_component(entropyk.Compressor())
d = system.add_component(entropyk.Condenser())
e = system.add_component(entropyk.ExpansionValve())
v = system.add_component(entropyk.Evaporator())
system.add_edge(c, d)
system.add_edge(d, e)
system.add_edge(e, v)
system.add_edge(v, c)
elapsed = time.perf_counter() - start
assert elapsed < 1.0, f"100 system builds took {elapsed:.3f}s"

View File

@@ -0,0 +1,248 @@
"""Entropyk — Unit Tests for Component Wrappers.
Tests for all component constructors, validation, and repr.
"""
import pytest
import entropyk
class TestCompressor:
"""Tests for Compressor component."""
def test_default(self):
c = entropyk.Compressor()
assert "Compressor" in repr(c)
def test_custom_params(self):
c = entropyk.Compressor(speed_rpm=3600.0, efficiency=0.9, fluid="R410A")
assert c.speed == 3600.0
assert c.efficiency_value == pytest.approx(0.9)
assert c.fluid_name == "R410A"
def test_negative_speed_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Compressor(speed_rpm=-1.0)
def test_negative_displacement_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Compressor(displacement=-1.0)
def test_invalid_efficiency_raises(self):
with pytest.raises(ValueError, match="between"):
entropyk.Compressor(efficiency=1.5)
def test_repr(self):
c = entropyk.Compressor(speed_rpm=2900.0, efficiency=0.85, fluid="R134a")
r = repr(c)
assert "2900" in r
assert "0.85" in r
assert "R134a" in r
class TestCondenser:
"""Tests for Condenser component."""
def test_default(self):
c = entropyk.Condenser()
assert c.ua_value == pytest.approx(5000.0)
def test_custom_ua(self):
c = entropyk.Condenser(ua=10000.0)
assert c.ua_value == pytest.approx(10000.0)
def test_negative_ua_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Condenser(ua=-1.0)
def test_repr(self):
c = entropyk.Condenser(ua=5000.0)
assert "Condenser" in repr(c)
assert "5000" in repr(c)
class TestEvaporator:
"""Tests for Evaporator component."""
def test_default(self):
e = entropyk.Evaporator()
assert e.ua_value == pytest.approx(3000.0)
def test_custom_ua(self):
e = entropyk.Evaporator(ua=8000.0)
assert e.ua_value == pytest.approx(8000.0)
def test_negative_ua_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Evaporator(ua=-1.0)
def test_repr(self):
e = entropyk.Evaporator(ua=3000.0)
assert "Evaporator" in repr(e)
class TestEconomizer:
"""Tests for Economizer component."""
def test_default(self):
e = entropyk.Economizer()
assert "Economizer" in repr(e)
def test_custom_ua(self):
e = entropyk.Economizer(ua=5000.0)
assert "5000" in repr(e)
def test_negative_ua_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Economizer(ua=-1.0)
class TestExpansionValve:
"""Tests for ExpansionValve component."""
def test_default(self):
v = entropyk.ExpansionValve()
assert v.fluid_name == "R134a"
assert v.opening_value is None
def test_with_opening(self):
v = entropyk.ExpansionValve(opening=0.5)
assert v.opening_value == pytest.approx(0.5)
def test_invalid_opening_raises(self):
with pytest.raises(ValueError, match="between"):
entropyk.ExpansionValve(opening=1.5)
def test_repr(self):
v = entropyk.ExpansionValve(fluid="R410A", opening=0.8)
assert "ExpansionValve" in repr(v)
assert "R410A" in repr(v)
class TestPipe:
"""Tests for Pipe component."""
def test_default(self):
p = entropyk.Pipe()
assert "Pipe" in repr(p)
def test_custom_params(self):
p = entropyk.Pipe(length=5.0, diameter=0.025)
assert "5.00" in repr(p)
def test_negative_length_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Pipe(length=-1.0)
def test_negative_diameter_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Pipe(diameter=-0.01)
class TestPump:
"""Tests for Pump component."""
def test_default(self):
p = entropyk.Pump()
assert "Pump" in repr(p)
def test_negative_pressure_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Pump(pressure_rise_pa=-100.0)
def test_invalid_efficiency_raises(self):
with pytest.raises(ValueError, match="between"):
entropyk.Pump(efficiency=2.0)
class TestFan:
"""Tests for Fan component."""
def test_default(self):
f = entropyk.Fan()
assert "Fan" in repr(f)
def test_negative_pressure_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.Fan(pressure_rise_pa=-100.0)
class TestFlowSplitter:
"""Tests for FlowSplitter component."""
def test_default(self):
s = entropyk.FlowSplitter()
assert "FlowSplitter" in repr(s)
def test_custom_outlets(self):
s = entropyk.FlowSplitter(n_outlets=3)
assert "3" in repr(s)
def test_too_few_outlets_raises(self):
with pytest.raises(ValueError, match=">="):
entropyk.FlowSplitter(n_outlets=1)
class TestFlowMerger:
"""Tests for FlowMerger component."""
def test_default(self):
m = entropyk.FlowMerger()
assert "FlowMerger" in repr(m)
def test_custom_inlets(self):
m = entropyk.FlowMerger(n_inlets=4)
assert "4" in repr(m)
def test_too_few_inlets_raises(self):
with pytest.raises(ValueError, match=">="):
entropyk.FlowMerger(n_inlets=1)
class TestFlowSource:
"""Tests for FlowSource component."""
def test_default(self):
s = entropyk.FlowSource()
assert "FlowSource" in repr(s)
def test_custom(self):
s = entropyk.FlowSource(pressure_pa=200000.0, temperature_k=350.0)
assert "200000" in repr(s)
def test_negative_pressure_raises(self):
with pytest.raises(ValueError, match="positive"):
entropyk.FlowSource(pressure_pa=-1.0)
class TestFlowSink:
"""Tests for FlowSink component."""
def test_default(self):
s = entropyk.FlowSink()
assert "FlowSink" in repr(s)
class TestOperationalState:
"""Tests for OperationalState enum."""
def test_on(self):
s = entropyk.OperationalState("on")
assert str(s) == "On"
def test_off(self):
s = entropyk.OperationalState("off")
assert str(s) == "Off"
def test_bypass(self):
s = entropyk.OperationalState("bypass")
assert str(s) == "Bypass"
def test_invalid_raises(self):
with pytest.raises(ValueError, match="one of"):
entropyk.OperationalState("invalid")
def test_eq(self):
s1 = entropyk.OperationalState("on")
s2 = entropyk.OperationalState("on")
assert s1 == s2

View File

@@ -0,0 +1,96 @@
"""Entropyk — Unit Tests for Exception Hierarchy.
Tests that all exception types exist, inherit correctly, and carry messages.
"""
import pytest
import entropyk
class TestExceptionHierarchy:
"""Tests for Python exception class hierarchy."""
def test_entropyk_error_exists(self):
assert hasattr(entropyk, "EntropykError")
assert issubclass(entropyk.EntropykError, Exception)
def test_solver_error_inherits(self):
assert issubclass(entropyk.SolverError, entropyk.EntropykError)
def test_timeout_error_inherits(self):
assert issubclass(entropyk.TimeoutError, entropyk.SolverError)
def test_control_saturation_error_inherits(self):
assert issubclass(entropyk.ControlSaturationError, entropyk.SolverError)
def test_fluid_error_inherits(self):
assert issubclass(entropyk.FluidError, entropyk.EntropykError)
def test_component_error_inherits(self):
assert issubclass(entropyk.ComponentError, entropyk.EntropykError)
def test_topology_error_inherits(self):
assert issubclass(entropyk.TopologyError, entropyk.EntropykError)
def test_validation_error_inherits(self):
assert issubclass(entropyk.ValidationError, entropyk.EntropykError)
class TestExceptionMessages:
"""Tests that exceptions carry descriptive messages."""
def test_entropyk_error_message(self):
err = entropyk.EntropykError("test message")
assert str(err) == "test message"
def test_solver_error_message(self):
err = entropyk.SolverError("convergence failed")
assert "convergence failed" in str(err)
def test_timeout_error_message(self):
err = entropyk.TimeoutError("timed out after 5s")
assert "timed out" in str(err)
def test_fluid_error_message(self):
err = entropyk.FluidError("R134a not found")
assert "R134a" in str(err)
def test_topology_error_message(self):
err = entropyk.TopologyError("graph cycle detected")
assert "cycle" in str(err)
class TestExceptionCatching:
"""Tests that exceptions can be caught at different hierarchy levels."""
def test_catch_solver_as_entropyk(self):
with pytest.raises(entropyk.EntropykError):
raise entropyk.SolverError("test")
def test_catch_timeout_as_solver(self):
with pytest.raises(entropyk.SolverError):
raise entropyk.TimeoutError("test")
def test_catch_timeout_as_entropyk(self):
with pytest.raises(entropyk.EntropykError):
raise entropyk.TimeoutError("test")
def test_catch_fluid_as_entropyk(self):
with pytest.raises(entropyk.EntropykError):
raise entropyk.FluidError("test")
def test_catch_component_as_entropyk(self):
with pytest.raises(entropyk.EntropykError):
raise entropyk.ComponentError("test")
def test_timeout_not_caught_as_fluid(self):
"""TimeoutError should NOT be caught by FluidError."""
with pytest.raises(entropyk.TimeoutError):
raise entropyk.TimeoutError("test")
# Verify it doesn't match FluidError
try:
raise entropyk.TimeoutError("test")
except entropyk.FluidError:
pytest.fail("TimeoutError should not be caught by FluidError")
except entropyk.TimeoutError:
pass # Expected

View File

@@ -0,0 +1,72 @@
"""Entropyk — NumPy / Buffer Protocol Tests.
Tests for zero-copy state vector access and NumPy integration.
"""
import pytest
import entropyk
# numpy may not be installed in test env — skip gracefully
numpy = pytest.importorskip("numpy")
class TestStateVectorNumpy:
"""Tests for state vector as NumPy array."""
def test_state_vector_to_numpy(self):
"""ConvergedState.state_vector returns a list convertible to np array."""
# Build a minimal system so we can get a state vector length
system = entropyk.System()
system.add_component(entropyk.Condenser())
system.add_component(entropyk.Evaporator())
system.add_edge(0, 1)
system.add_edge(1, 0)
system.finalize()
# The state_vector_len should be > 0 after finalize
svl = system.state_vector_len
assert svl >= 0
def test_converged_state_vector_is_list(self):
"""The state_vector attribute on ConvergedState should be a Python list
of floats, convertible to numpy.array."""
# We can't solve without real physics, but we can verify the accessor type
# from the class itself
assert hasattr(entropyk, "ConvergedState")
def test_numpy_array_from_list(self):
"""Verify that a list of floats (as returned by state_vector) can be
efficiently converted to a numpy array."""
data = [1.0, 2.0, 3.0, 4.0, 5.0]
arr = numpy.array(data, dtype=numpy.float64)
assert arr.shape == (5,)
assert arr.dtype == numpy.float64
numpy.testing.assert_array_almost_equal(arr, data)
class TestTypesWithNumpy:
"""Tests for using core types with NumPy."""
def test_pressure_float_in_numpy(self):
"""Pressure can be used as a float value in numpy operations."""
p = entropyk.Pressure(bar=1.0)
arr = numpy.array([float(p)], dtype=numpy.float64)
assert arr[0] == pytest.approx(100000.0)
def test_temperature_float_in_numpy(self):
"""Temperature can be used as a float value in numpy operations."""
t = entropyk.Temperature(celsius=25.0)
arr = numpy.array([float(t)], dtype=numpy.float64)
assert arr[0] == pytest.approx(298.15)
def test_enthalpy_float_in_numpy(self):
"""Enthalpy can be used as a float value in numpy operations."""
h = entropyk.Enthalpy(kj_per_kg=250.0)
arr = numpy.array([float(h)], dtype=numpy.float64)
assert arr[0] == pytest.approx(250000.0)
def test_massflow_float_in_numpy(self):
"""MassFlow can be used as a float value in numpy operations."""
m = entropyk.MassFlow(kg_per_s=0.5)
arr = numpy.array([float(m)], dtype=numpy.float64)
assert arr[0] == pytest.approx(0.5)

View File

@@ -0,0 +1,147 @@
"""Entropyk — End-to-End Solver Tests.
Tests for System construction, finalization, and solving from Python.
"""
import pytest
import entropyk
class TestSystemConstruction:
"""Tests for System graph building."""
def test_empty_system(self):
system = entropyk.System()
assert system.node_count == 0
assert system.edge_count == 0
def test_add_component(self):
system = entropyk.System()
idx = system.add_component(entropyk.Condenser(ua=5000.0))
assert idx == 0
assert system.node_count == 1
def test_add_multiple_components(self):
system = entropyk.System()
i0 = system.add_component(entropyk.Compressor())
i1 = system.add_component(entropyk.Condenser())
i2 = system.add_component(entropyk.ExpansionValve())
i3 = system.add_component(entropyk.Evaporator())
assert system.node_count == 4
assert i0 != i1 != i2 != i3
def test_add_edge(self):
system = entropyk.System()
i0 = system.add_component(entropyk.Compressor())
i1 = system.add_component(entropyk.Condenser())
edge_idx = system.add_edge(i0, i1)
assert edge_idx == 0
assert system.edge_count == 1
def test_repr(self):
system = entropyk.System()
system.add_component(entropyk.Compressor())
system.add_component(entropyk.Condenser())
system.add_edge(0, 1)
r = repr(system)
assert "System" in r
assert "nodes=2" in r
assert "edges=1" in r
class TestSystemFinalize:
"""Tests for system finalization."""
def test_simple_cycle_finalize(self):
"""Build and finalize a simple 4-component cycle."""
system = entropyk.System()
comp = system.add_component(entropyk.Compressor())
cond = system.add_component(entropyk.Condenser())
exv = system.add_component(entropyk.ExpansionValve())
evap = system.add_component(entropyk.Evaporator())
system.add_edge(comp, cond)
system.add_edge(cond, exv)
system.add_edge(exv, evap)
system.add_edge(evap, comp)
system.finalize()
assert system.state_vector_len > 0
class TestSolverConfigs:
"""Tests for solver configuration objects."""
def test_newton_default(self):
config = entropyk.NewtonConfig()
assert "NewtonConfig" in repr(config)
assert "100" in repr(config)
def test_newton_custom(self):
config = entropyk.NewtonConfig(
max_iterations=200,
tolerance=1e-8,
line_search=True,
timeout_ms=5000,
)
assert "200" in repr(config)
def test_picard_default(self):
config = entropyk.PicardConfig()
assert "PicardConfig" in repr(config)
def test_picard_custom(self):
config = entropyk.PicardConfig(
max_iterations=300,
tolerance=1e-5,
relaxation=0.7,
)
assert "300" in repr(config)
def test_picard_invalid_relaxation_raises(self):
with pytest.raises(ValueError, match="between"):
entropyk.PicardConfig(relaxation=1.5)
def test_fallback_default(self):
config = entropyk.FallbackConfig()
assert "FallbackConfig" in repr(config)
def test_fallback_custom(self):
newton = entropyk.NewtonConfig(max_iterations=50)
picard = entropyk.PicardConfig(max_iterations=200)
config = entropyk.FallbackConfig(newton=newton, picard=picard)
assert "50" in repr(config)
class TestConvergedState:
"""Tests for ConvergedState and ConvergenceStatus types."""
def test_convergence_status_repr(self):
# We can't easily create a ConvergedState without solving,
# so we just verify the classes exist
assert hasattr(entropyk, "ConvergedState")
assert hasattr(entropyk, "ConvergenceStatus")
class TestAllComponentsInSystem:
"""Test that all component types can be added to a System."""
@pytest.mark.parametrize("component_factory", [
lambda: entropyk.Compressor(),
lambda: entropyk.Condenser(),
lambda: entropyk.Evaporator(),
lambda: entropyk.Economizer(),
lambda: entropyk.ExpansionValve(),
lambda: entropyk.Pipe(),
lambda: entropyk.Pump(),
lambda: entropyk.Fan(),
lambda: entropyk.FlowSplitter(),
lambda: entropyk.FlowMerger(),
lambda: entropyk.FlowSource(),
lambda: entropyk.FlowSink(),
])
def test_add_component(self, component_factory):
system = entropyk.System()
idx = system.add_component(component_factory())
assert idx >= 0
assert system.node_count == 1

View File

@@ -0,0 +1,208 @@
"""Entropyk — Unit Tests for Core Physical Types.
Tests for Pressure, Temperature, Enthalpy, and MassFlow wrappers.
"""
import pytest
import entropyk
class TestPressure:
"""Tests for Pressure type."""
def test_from_pa(self):
p = entropyk.Pressure(pa=101325.0)
assert p.to_pascals() == pytest.approx(101325.0)
def test_from_bar(self):
p = entropyk.Pressure(bar=1.01325)
assert p.to_pascals() == pytest.approx(101325.0)
def test_from_kpa(self):
p = entropyk.Pressure(kpa=101.325)
assert p.to_pascals() == pytest.approx(101325.0)
def test_from_psi(self):
p = entropyk.Pressure(psi=14.696)
assert p.to_pascals() == pytest.approx(101325.0, rel=1e-3)
def test_to_bar(self):
p = entropyk.Pressure(pa=100000.0)
assert p.to_bar() == pytest.approx(1.0)
def test_to_kpa(self):
p = entropyk.Pressure(pa=1000.0)
assert p.to_kpa() == pytest.approx(1.0)
def test_float(self):
p = entropyk.Pressure(pa=101325.0)
assert float(p) == pytest.approx(101325.0)
def test_repr(self):
p = entropyk.Pressure(bar=1.0)
assert "Pressure" in repr(p)
assert "bar" in repr(p)
def test_str(self):
p = entropyk.Pressure(pa=100.0)
assert "Pa" in str(p)
def test_eq(self):
p1 = entropyk.Pressure(bar=1.0)
p2 = entropyk.Pressure(bar=1.0)
assert p1 == p2
def test_add(self):
p1 = entropyk.Pressure(pa=100.0)
p2 = entropyk.Pressure(pa=200.0)
result = p1 + p2
assert float(result) == pytest.approx(300.0)
def test_sub(self):
p1 = entropyk.Pressure(pa=300.0)
p2 = entropyk.Pressure(pa=100.0)
result = p1 - p2
assert float(result) == pytest.approx(200.0)
def test_multiple_kwargs_raises(self):
with pytest.raises(ValueError, match="exactly one"):
entropyk.Pressure(pa=100.0, bar=1.0)
def test_no_kwargs_raises(self):
with pytest.raises(ValueError, match="exactly one"):
entropyk.Pressure()
class TestTemperature:
"""Tests for Temperature type."""
def test_from_kelvin(self):
t = entropyk.Temperature(kelvin=300.0)
assert t.to_kelvin() == pytest.approx(300.0)
def test_from_celsius(self):
t = entropyk.Temperature(celsius=25.0)
assert t.to_kelvin() == pytest.approx(298.15)
def test_from_fahrenheit(self):
t = entropyk.Temperature(fahrenheit=77.0)
assert t.to_celsius() == pytest.approx(25.0)
def test_to_celsius(self):
t = entropyk.Temperature(kelvin=273.15)
assert t.to_celsius() == pytest.approx(0.0)
def test_to_fahrenheit(self):
t = entropyk.Temperature(celsius=100.0)
assert t.to_fahrenheit() == pytest.approx(212.0)
def test_float(self):
t = entropyk.Temperature(kelvin=300.0)
assert float(t) == pytest.approx(300.0)
def test_repr(self):
t = entropyk.Temperature(celsius=25.0)
assert "Temperature" in repr(t)
def test_eq(self):
t1 = entropyk.Temperature(celsius=25.0)
t2 = entropyk.Temperature(celsius=25.0)
assert t1 == t2
def test_add(self):
t1 = entropyk.Temperature(kelvin=100.0)
t2 = entropyk.Temperature(kelvin=200.0)
result = t1 + t2
assert float(result) == pytest.approx(300.0)
def test_sub(self):
t1 = entropyk.Temperature(kelvin=300.0)
t2 = entropyk.Temperature(kelvin=100.0)
result = t1 - t2
assert float(result) == pytest.approx(200.0)
def test_multiple_kwargs_raises(self):
with pytest.raises(ValueError, match="exactly one"):
entropyk.Temperature(kelvin=300.0, celsius=25.0)
class TestEnthalpy:
"""Tests for Enthalpy type."""
def test_from_j_per_kg(self):
h = entropyk.Enthalpy(j_per_kg=250000.0)
assert h.to_j_per_kg() == pytest.approx(250000.0)
def test_from_kj_per_kg(self):
h = entropyk.Enthalpy(kj_per_kg=250.0)
assert h.to_j_per_kg() == pytest.approx(250000.0)
def test_to_kj_per_kg(self):
h = entropyk.Enthalpy(j_per_kg=250000.0)
assert h.to_kj_per_kg() == pytest.approx(250.0)
def test_float(self):
h = entropyk.Enthalpy(j_per_kg=250000.0)
assert float(h) == pytest.approx(250000.0)
def test_repr(self):
h = entropyk.Enthalpy(kj_per_kg=250.0)
assert "Enthalpy" in repr(h)
def test_eq(self):
h1 = entropyk.Enthalpy(kj_per_kg=250.0)
h2 = entropyk.Enthalpy(kj_per_kg=250.0)
assert h1 == h2
def test_add(self):
h1 = entropyk.Enthalpy(j_per_kg=100.0)
h2 = entropyk.Enthalpy(j_per_kg=200.0)
result = h1 + h2
assert float(result) == pytest.approx(300.0)
def test_sub(self):
h1 = entropyk.Enthalpy(j_per_kg=300.0)
h2 = entropyk.Enthalpy(j_per_kg=100.0)
result = h1 - h2
assert float(result) == pytest.approx(200.0)
class TestMassFlow:
"""Tests for MassFlow type."""
def test_from_kg_per_s(self):
m = entropyk.MassFlow(kg_per_s=0.5)
assert m.to_kg_per_s() == pytest.approx(0.5)
def test_from_g_per_s(self):
m = entropyk.MassFlow(g_per_s=500.0)
assert m.to_kg_per_s() == pytest.approx(0.5)
def test_to_g_per_s(self):
m = entropyk.MassFlow(kg_per_s=0.5)
assert m.to_g_per_s() == pytest.approx(500.0)
def test_float(self):
m = entropyk.MassFlow(kg_per_s=0.5)
assert float(m) == pytest.approx(0.5)
def test_repr(self):
m = entropyk.MassFlow(kg_per_s=0.5)
assert "MassFlow" in repr(m)
def test_eq(self):
m1 = entropyk.MassFlow(kg_per_s=0.5)
m2 = entropyk.MassFlow(kg_per_s=0.5)
assert m1 == m2
def test_add(self):
m1 = entropyk.MassFlow(kg_per_s=0.1)
m2 = entropyk.MassFlow(kg_per_s=0.2)
result = m1 + m2
assert float(result) == pytest.approx(0.3)
def test_sub(self):
m1 = entropyk.MassFlow(kg_per_s=0.5)
m2 = entropyk.MassFlow(kg_per_s=0.2)
result = m1 - m2
assert float(result) == pytest.approx(0.3)