def __init__(self, default_temp_shift=None): self._default_temp_shift = default_temp_shift self._streams = [] self._pinch_temps = [] self._cold_utility_target = 0 self._hot_utility_target = 0 self._cold_cascade = HeatCascade() self._hot_cascade = HeatCascade() self._shifted_cold_cascade = HeatCascade() self._shifted_hot_cascade = HeatCascade() self._grand_cascade = HeatCascade()
def test_hot_sensible_segment(self): hot_segment = make_segment(180, 150, 50) cascade = HeatCascade() cascade.add_segments(hot_segment) self.assertEqual(cascade.intervals, [make_segment(180, 50, 150)]) self.assertEqual(cascade.net_heat_flow(), 180) self.assertEqual(cascade.cumulative_heat_flow(), ([0, 180], [50, 150])) self.assertEqual(cascade.net_heat_flow(), 180) self.assertEqual(cascade.cumulative_heat_flow(-180), ([-180, 0], [50, 150]))
def test_neutral_latent_segment(self): neutral_segment = make_segment(0, 80, 80) cascade = HeatCascade() cascade.add_segments(neutral_segment) self.assertEqual(cascade.intervals, []) self.assertEqual(cascade.net_heat_flow(), 0) self.assertEqual(cascade.cumulative_heat_flow(), ([], [])) self.assertEqual(cascade.cumulative_heat_flow(-100), ([], []))
def test_cold_sensible_segment(self): cold_segment = make_segment(-180, 20, 200) cascade = HeatCascade() cascade.add_segments(cold_segment) self.assertEqual(cascade.intervals, [cold_segment]) self.assertEqual(cascade.net_heat_flow(), -180) self.assertEqual(cascade.cumulative_heat_flow(), ([0, -180], [20, 200])) self.assertEqual(cascade.cumulative_heat_flow(50), ([50, -130], [20, 200]))
def test_cold_latent_segment(self): cold_segment = make_segment(-200, 100, 100) cascade = HeatCascade() cascade.add_segments(cold_segment) self.assertEqual(cascade.intervals, [cold_segment]) self.assertEqual(cascade.net_heat_flow(), -200) self.assertEqual(cascade.cumulative_heat_flow(), ([0, -200], [100, 100])) self.assertEqual(cascade.cumulative_heat_flow(-50), ([-50, -250], [100, 100]))
def test_hot_latent_segment(self): hot_segment = make_segment(300, 150, 150) cascade = HeatCascade() cascade.add_segments(hot_segment) self.assertEqual(cascade.intervals, [hot_segment]) self.assertEqual(cascade.net_heat_flow(), 300) self.assertEqual(cascade.cumulative_heat_flow(), ([0, 300], [150, 150])) self.assertEqual(cascade.cumulative_heat_flow(100), ([100, 400], [150, 150]))
def test_link_hot_sensible_segments(self): hot_segments = [make_segment(80, 120, 80), make_segment(120, 80, 20)] cascade = HeatCascade() cascade.add_segments(*hot_segments) self.assertEqual(cascade.intervals, [make_segment(200, 20, 120)]) self.assertEqual(cascade.net_heat_flow(), 200) self.assertEqual(cascade.cumulative_heat_flow(), ([0, 200], [20, 120]))
def test_neutralized_heat_flow(self): neutralizing_segments = [ make_segment(-60, 60, 120), make_segment(60, 120, 60) ] cascade = HeatCascade() cascade.add_segments(*neutralizing_segments) self.assertEqual(cascade.intervals, []) self.assertEqual(cascade.net_heat_flow(), 0) self.assertEqual(cascade.cumulative_heat_flow(), ([], []))
def test_detached_segments(self): detached_segments = [ make_segment(-30, 20, 50), make_segment(-80, 80, 120) ] cascade = HeatCascade() cascade.add_segments(*detached_segments) self.assertEqual(cascade.intervals, detached_segments) self.assertEqual(cascade.net_heat_flow(), -110) self.assertEqual(cascade.cumulative_heat_flow(), ([0, -30, -30, -110], [20, 50, 80, 120]))
def test_overlapping_hot_segments(self): hot_segments = [make_segment(60, 120, 60), make_segment(130, 85, 20)] expected_intervals = [ make_segment(80, 20, 60), make_segment(75, 60, 85), make_segment(35, 85, 120), ] cascade = HeatCascade() cascade.add_segments(*hot_segments) self.assertEqual(cascade.intervals, expected_intervals) self.assertEqual(cascade.net_heat_flow(), 190) self.assertEqual(cascade.cumulative_heat_flow(), ([0, 80, 155, 190], [20, 60, 85, 120]))
def test_link_latent_segments(self): latent_segments = [ make_segment(-200, 100, 100), make_segment(-150, 100, 100), make_segment(250, 100, 100), ] expected_intervals = [make_segment(-100, 100, 100)] cascade = HeatCascade() cascade.add_segments(*latent_segments) self.assertEqual(cascade.intervals, expected_intervals) self.assertEqual(cascade.net_heat_flow(), -100) self.assertEqual(cascade.cumulative_heat_flow(), ([0, -100], [100, 100]))
def test_detached_latent_segments(self): latent_segments = [ make_segment(-200, 100, 100), make_segment(-150, 200, 200) ] cascade = HeatCascade() cascade.add_segments(*latent_segments) self.assertEqual(cascade.intervals, latent_segments) self.assertEqual(cascade.net_heat_flow(), -350) self.assertEqual( cascade.cumulative_heat_flow(), ([0, -200, -200, -350], [100, 100, 200, 200]), )
def test_add_latent_then_sensible_segment(self): latent_then_sensible = HeatCascade() latent_then_sensible.add_segments(make_segment(-200, 100, 100), make_segment(-80, 80, 120)) expected_intervals = [ make_segment(-40, 80, 100), make_segment(-200, 100, 100), make_segment(-40, 100, 120), ] self.assertEqual(latent_then_sensible.intervals, expected_intervals) self.assertEqual(latent_then_sensible.net_heat_flow(), -280) self.assertEqual( latent_then_sensible.cumulative_heat_flow(), ([0, -40, -240, -280], [80, 100, 100, 120]), )
def test_overlapping_cold_segments(self): cold_segments = [ make_segment(-65, 20, 85), make_segment(-120, 60, 120) ] expected_intervals = [ make_segment(-40, 20, 60), make_segment(-75, 60, 85), make_segment(-70, 85, 120), ] cascade = HeatCascade() cascade.add_segments(*cold_segments) self.assertEqual(cascade.intervals, expected_intervals) self.assertEqual(cascade.net_heat_flow(), -185) self.assertEqual(cascade.cumulative_heat_flow(), ([0, -40, -115, -185], [20, 60, 85, 120]))
def test_mixed_sensible_segments(self): mixed_segments = [ make_segment(-60, 60, 120), make_segment(-10, 60, 70), make_segment(130, 85, 20), ] expected_intervals = [ make_segment(80, 20, 60), make_segment(15, 70, 85), make_segment(-35, 85, 120), ] cascade = HeatCascade() cascade.add_segments(*mixed_segments) self.assertEqual(cascade.intervals, expected_intervals) self.assertEqual(cascade.net_heat_flow(), 60) self.assertEqual(cascade.cumulative_heat_flow(), ([0, 80, 80, 95, 60], [20, 60, 70, 85, 120]))
def test_add_cascades(self): cold_cascade = HeatCascade() cold_cascade.add_segments(make_segment(-230, 25, 140)) cold_cascade.add_segments(make_segment(-240, 85, 145)) hot_cascade = HeatCascade() hot_cascade.add_segments(make_segment(330, 165, 55)) hot_cascade.add_segments(make_segment(180, 145, 25)) mixed_cascade = HeatCascade() mixed_cascade.add_segments(*cold_cascade.intervals) mixed_cascade.add_segments(*hot_cascade.intervals) self.assertEqual( mixed_cascade.intervals, [ make_segment(-15, 25, 55), make_segment(75, 55, 85), make_segment(-82.5, 85, 140), make_segment(2.5, 140, 145), make_segment(60, 145, 165), ], ) self.assertEqual(mixed_cascade.net_heat_flow(), 40) self.assertEqual( mixed_cascade.cumulative_heat_flow(), ([0, -15, 60, -22.5, -20, 40], [25, 55, 85, 140, 145, 165]), ) self.assertEqual( mixed_cascade.cumulative_heat_flow(22.5), ([22.5, 7.5, 82.5, 0, 2.5, 62.5], [25, 55, 85, 140, 145, 165]), )
class PinchAnalyzer: """ This class performs pinch analysis calculations on a list of streams: heating and cooling requirements, heat cascades and the pinch temperature(s). """ def __init__(self, default_temp_shift=None): self._default_temp_shift = default_temp_shift self._streams = [] self._pinch_temps = [] self._cold_utility_target = 0 self._hot_utility_target = 0 self._cold_cascade = HeatCascade() self._hot_cascade = HeatCascade() self._shifted_cold_cascade = HeatCascade() self._shifted_hot_cascade = HeatCascade() self._grand_cascade = HeatCascade() @property def cooling_demand(self): return self._hot_cascade.net_heat_flow() @property def heating_demand(self): return self._cold_cascade.net_heat_flow() @property def cold_utility_target(self): return self._cold_utility_target @property def hot_utility_target(self): return self._hot_utility_target @property def heat_recovery_target(self): return self.heating_demand - self.hot_utility_target @property def pinch_temps(self): return self._pinch_temps @property def default_temp_shift(self): return self._default_temp_shift @property def streams(self): return self._streams @property def cold_composite_curve(self): return self._cold_cascade.cumulative_heat_flow( self.cold_utility_target) @property def hot_composite_curve(self): return self._hot_cascade.cumulative_heat_flow() @property def shifted_cold_composite_curve(self): return self._shifted_cold_cascade.cumulative_heat_flow( self.cold_utility_target) @property def shifted_hot_composite_curve(self): return self._shifted_hot_cascade.cumulative_heat_flow() @property def grand_composite_curve(self): return self._grand_cascade.cumulative_heat_flow( self.cold_utility_target) def add_streams(self, *streams): """ Adds the given streams to the stream group. """ for s in streams: self._add_one(s) self._compute_targets() def _add_one(self, stream): self._streams.append(stream) for s in stream.cold_segments: self._cold_cascade.add_segments(s.with_absolute_heat_flow()) shifted = s.shift(self.default_temp_shift) self._shifted_cold_cascade.add_segments( shifted.with_absolute_heat_flow()) self._grand_cascade.add_segments(shifted.with_inverted_heat_flow()) for s in stream.hot_segments: self._hot_cascade.add_segments(s.with_absolute_heat_flow()) shifted = s.shift(self.default_temp_shift) self._shifted_hot_cascade.add_segments( shifted.with_absolute_heat_flow()) self._grand_cascade.add_segments(shifted.with_inverted_heat_flow()) def _compute_targets(self): heat_flows, temps = self._grand_cascade.cumulative_heat_flow() if heat_flows: min_heat_flow = min(heat_flows) self._pinch_temps = [ t for t, h in zip(temps, heat_flows) if h == min_heat_flow ] self._cold_utility_target = heat_flows[0] - min_heat_flow self._hot_utility_target = heat_flows[-1] - min_heat_flow else: self._pinch_temps = [] self._cold_utility_target = 0 self._hot_utility_target = 0
def test_touching_segments(self): cold_segments = [make_segment(-60, 20, 80), make_segment(-80, 80, 120)] cold_cascade = HeatCascade() cold_cascade.add_segments(*cold_segments) self.assertEqual(cold_cascade.intervals, cold_segments) self.assertEqual(cold_cascade.net_heat_flow(), -140) self.assertEqual(cold_cascade.cumulative_heat_flow(), ([0, -60, -140], [20, 80, 120])) hot_segments = [make_segment(80, 120, 80), make_segment(60, 80, 20)] hot_cascade = HeatCascade() hot_cascade.add_segments(*hot_segments) self.assertEqual(hot_cascade.intervals, [make_segment(60, 20, 80), make_segment(80, 80, 120)]) self.assertEqual(hot_cascade.net_heat_flow(), 140) self.assertEqual(hot_cascade.cumulative_heat_flow(), ([0, 60, 140], [20, 80, 120]))
def test_empty(self): cascade = HeatCascade() self.assertEqual(cascade.intervals, []) self.assertEqual(cascade.net_heat_flow(), 0) self.assertEqual(cascade.cumulative_heat_flow(), ([], [])) self.assertEqual(cascade.cumulative_heat_flow(-100), ([], []))
def test_equality_comparison(self): cascade = HeatCascade() self.assertEqual(cascade, HeatCascade()) compare_to = HeatCascade() compare_to.add_segments(make_segment(-230, 25, 140), make_segment(-240, 85, 145)) self.assertNotEqual(cascade, compare_to) cascade.add_segments(make_segment(-230, 25, 140)) self.assertNotEqual(cascade, compare_to) cascade.add_segments(make_segment(-240, 85, 145)) self.assertEqual(cascade, compare_to)