Skip to content

Commit

Permalink
Merge pull request #1404 from quintel/heat-final-fixes
Browse files Browse the repository at this point in the history
Fixes for several issues with new heat modelling in built environment
  • Loading branch information
mabijkerk authored Feb 20, 2024
2 parents fc2e614 + af48e9c commit 3f0017f
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 42 deletions.
8 changes: 6 additions & 2 deletions app/models/gql/runtime/functions/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def update_something_by(*value_terms)
scope.update_object = object # for UPDATE_OBJECT()
input_value = input_value_proc.respond_to?(:call) ? input_value_proc.call : input_value_proc
input_value = input_value.first if input_value.is_a?(::Array)
original_value = object[attribute_name]
original_value = object[attribute_name.to_sym]

if original_value.is_a?(Numeric)
original_value = original_value.to_f
Expand All @@ -145,7 +145,11 @@ def update_something_by(*value_terms)

# @private
def update_element_with(object, attribute_name, value)
object.send "#{attribute_name}=", value
if object.is_a?(Hash) && object.key?(attribute_name.to_sym)
object[attribute_name.to_sym] = value
else
object.send("#{attribute_name}=", value)
end
end

# Private: at the moment only takes care of percentages and absolute numbers.
Expand Down
3 changes: 1 addition & 2 deletions app/models/qernel/fever_facade/calculators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ def initialize(ordered_producers, ordered_consumers, context)
@ordered_consumers = ordered_consumers
@ordered_producers = ordered_producers

# first build then refactor
# make sure we can forget what we don't need
setup
end

Expand Down Expand Up @@ -57,6 +55,7 @@ def setup
producer_share -= consumer_share

consumer.build_activity(producer, (consumer_share / consumer_share_in_total))
consumer.inject_share_to_producer(producer, (consumer_share / consumer_share_in_total))
end
end
end
Expand Down
11 changes: 5 additions & 6 deletions app/models/qernel/fever_facade/consumer_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ class ConsumerAdapter < Adapter
def initialize(node, context)
super
@was = @node.demand
# TODO: at the init we should set all edges shares to 0!!!
# Then set them again -rigth? or is it not neccesary? how does future graph work? they are unset right?
# Set all edges to 0.0 to ensure that all edges set in the setup phase sum to 1.0
# after calculating the consumer producer pairs
@node.input(:useable_heat).edges.each { |e| e.share = 0.0 }
end

# How much has the consumer already been filled with
# How much has the consumer already been filled with (could also be sum of input_edges)
def share_met
@share_met ||= 0.0
# Of kan dat toch vanuit de summed edges zoals we in de vorige commit hadden?
end

# Injects share after to calculation
Expand All @@ -42,8 +42,7 @@ def participant_for(tech_type, share)
end

def inject!
# TODO: this feels very very nasty, but I think we have to! Otherwise the
# graph wil try to solve it himself based on something unknown
# Inject shares again
@node.input(:useable_heat).edges.each { |e| e.share = 0.0 }

calculable_activities.each_value do |calc_activity|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ def demand_curve_from_activities
def production_curve_from_activities
Merit::CurveTools.add_curves(
calculable_activities.filter_map do |_, activity|
activity.activities.sum(&:production_curve) unless activity.empty?
unless activity.empty?
Merit::CurveTools.add_curves(activity.activities.map(&:production_curve))
end
end
) || EMPTY_CURVE
end
Expand Down
23 changes: 16 additions & 7 deletions app/models/qernel/fever_facade/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module FeverFacade
# Fever instance.
class Group
attr_reader :name
attr_reader :calculators

# Public: Creates a new Group.
#
Expand All @@ -17,13 +18,8 @@ class Group
def initialize(name, plugin)
@name = name
@context = Context.new(plugin, plugin.graph)
end

# Public: Sets up calculators where consumers and producers are matched
def calculators
@calculators ||= Qernel::FeverFacade::Calculators.new(
ordered_producers, ordered_consumers, @context
)
setup_calculators
end

# Public: Instructs the calculator to compute a single frame.
Expand Down Expand Up @@ -62,7 +58,20 @@ def ordered_consumers
end

def producer_adapters
adapters.select { |adapter| adapter.config.type == :producer }
adapters.select { |adapter| adapter.is_a?(Qernel::FeverFacade::ProducerAdapter) }
end

def consumer_adapters
adapters.select { |adapter| adapter.is_a?(Qernel::FeverFacade::ConsumerAdapter) }
end

private

# Private: Sets up calculators where consumers and producers are matched
def setup_calculators
@calculators = Qernel::FeverFacade::Calculators.new(
ordered_producers, ordered_consumers, @context
)
end
end
end
Expand Down
5 changes: 4 additions & 1 deletion app/models/qernel/fever_facade/producer_adapter/inject.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ module Inject
def inject_demand!
production = producer.output_curve.sum

# TODO: get aggerate curve etc.
inject_aggregator_attributes!(production)

# MWh -> MJ
Expand Down Expand Up @@ -50,6 +49,10 @@ def inject_aggregator_attributes!(production)

# TODO: should be a method on collection of participants
demand = participants.sum(&:demand)
# puts @node.key
# puts demand
# puts production
# debugger if @node.key == :buildings_space_heater_heatpump_air_water_network_gas
edge = conv.output(:useable_heat).edges.first

edge.share = demand.positive? ? production / demand : 0.0
Expand Down
12 changes: 7 additions & 5 deletions app/models/qernel/fever_facade/summary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def demand

suppliers =
@group.adapters.select do |adapter|
adapter.installed? && adapter.participant.is_a?(Fever::Consumer)
adapter.installed? && adapter.is_a?(Qernel::FeverFacade::ConsumerAdapter)
end

return [0.0] * 8760 if suppliers.none?
Expand All @@ -34,10 +34,7 @@ def demand
def production
return @production if @production

suppliers =
@group.adapters.reject do |adapter|
adapter.participant.is_a?(Fever::Consumer)
end
suppliers = @group.producer_adapters

return [0.0] * 8760 if suppliers.none?

Expand Down Expand Up @@ -138,6 +135,11 @@ def total_production_curve_for_producer(producer_key)
producer.producer.output_curve
end

# In the summary and export first the consumers and then the producers are shown
def nodes
@group.consumer_adapters.map(&:node) + @group.producer_adapters.map(&:node)
end

private

# Internal: Compares production and demand and creates two new curves
Expand Down
2 changes: 1 addition & 1 deletion app/models/qernel/merit_facade/consumption_loss_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def loss_share
end

edge = edges.first
loss_share = edge.rgt_output.conversion * edge.parent_share
loss_share = edge.rgt_output.conversion

other_shares =
edge.rgt_node.outputs.sum do |input|
Expand Down
6 changes: 5 additions & 1 deletion app/models/qernel/plugins/causality.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Causality
CURVE_ROTATE = 2160

before :first_calculation, :clone_dataset
before :first_calculation, :early_setup
after :first_calculation, :setup
after :first_calculation, :run
before :recalculation, :inject
Expand Down Expand Up @@ -57,7 +58,6 @@ def run(lifecycle)

# Internal: Sets up Fever and Merit.
def setup
@fever.setup
@merit.setup
@heat_network.setup
@agriculture_heat.setup
Expand All @@ -69,6 +69,10 @@ def setup
@reconciliation.setup_early
end

def early_setup
@fever.setup
end

def inject(lifecycle)
merit_calc = Merit::StepwiseCalculator.new.calculate(@merit.order)

Expand Down
62 changes: 46 additions & 16 deletions app/serializers/fever_csv_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
# Creates CSV rows describing heat demand and supply for one or more fever groups.
class FeverCSVSerializer
attr_reader :filename

def self.time_column(year)
# We don't model leap days: 1970 is a safe choice for accurate times in the CSV.
base_date = Time.utc(1970, 1, 1)

['Time'] +
Array.new(8760) do |i|
(base_date + i.hours).strftime("#{year}-%m-%d %R")
end
end

def initialize(graph, groups, filename)
@graph = graph
@groups = groups.map(&:to_sym)
Expand All @@ -16,32 +27,51 @@ def to_csv_rows
'enabled for this scenario']]
end

[headers, *data]
data
end

private

def headers
def columns_for(node, summary)
[
'Production (MW)',
'Demand (MW)',
'Buffering and time-shifting (MW)',
'Deficit (MW)'
demand_column_for(node, summary),
production_column_for(node, summary)
]
end

def data
curve(:production).zip(
curve(:demand),
curve(:surplus),
curve(:deficit)
)
def safe_curve(curve)
curve.empty? ? [0.0] * 8760 : curve
end

def demand_column_for(node, summary)
case node.fever.type
when :consumer
["#{node.key}.input.demand (MW)"] +
safe_curve(summary.total_demand_curve_for_consumer(node.key))
when :producer
["#{node.key}.output.demand (MW)"] +
safe_curve(summary.total_demand_curve_for_producer(node.key))
end
end

def production_column_for(node, summary)
case node.fever.type
when :consumer
["#{node.key}.input.production (MW)"] +
safe_curve(summary.total_production_curve_for_consumer(node.key))
when :producer
["#{node.key}.output.production (MW)"] +
safe_curve(summary.total_production_curve_for_producer(node.key))
end
end

def curve(type)
Merit::CurveTools.add_curves(
@groups.map { |group| summary(group).public_send(type) }
)
def data
(
[self.class.time_column(@graph.year)] + @groups.flat_map do |group|
summary = summary(group)
summary.nodes.flat_map { |node| columns_for(node, summary) }
end
).transpose
end

def summary(group)
Expand Down

0 comments on commit 3f0017f

Please sign in to comment.