<commit_before>module Qernel
##
#
#
class Link
  extend ActiveModel::Naming
  include Topology::Link

  # --------- Dataset ---------------------------------------------------------

  include DatasetAttributes

  DATASET_ATTRIBUTES = [
    :share, 
    :value, 
    :max_demand, 
    :calculated,
    :country_specific
  ]

  dataset_accessors DATASET_ATTRIBUTES


  # --------- Accessor ---------------------------------------------------------

  attr_accessor :graph, # needed for dataset
                :parent, # Parent is the converter to the left (towards useful demand)
                :child,  # Child is the converter to the right (towards useful primary demand)
                :carrier 

  attr_reader :id,
              :link_type


  # --------- Flow ------------------------------------------------------------

  attr_accessor :reversed
  attr_reader :is_loss

  alias reversed? reversed
  alias loss? is_loss


  # --------- Link Types ------------------------------------------------------

  attr_reader :is_share, :flexible, :dependent, :constant, :inversed_flexible

  alias share? is_share
  alias flexible? flexible
  alias dependent? dependent
  alias constant? constant
  alias inversed_flexible? inversed_flexible


  # --------- Initialize ------------------------------------------------------

  def initialize(id, input, output, carrier, link_type, reversed = false)
    @id = id.is_a?(Numeric) ? id : Hashpipe.hash(id)

    @reversed = reversed
    @parent, @child, @carrier = input, output, carrier
    
    self.link_type = link_type.to_sym

    connect
    memoize_for_cache
  end

protected

  def connect
    @parent.add_input_link(self) if @parent # only used in testing
    @child.add_output_link(self) if @child  # only used in testing
  end

  def memoize_for_cache
    @is_loss = @carrier.andand.id == 1

    self.dataset_key # memoize dataset_key
  end

  def link_type=(link_type)
    @link_type = link_type

    @is_share = @link_type === :share
    @flexible = @link_type === :flexible
    @inversed_flexible = @link_type === :inversed_flexible
    @dependent = @link_type === :dependent
    @constant = @link_type === :constant
  end

  def after_assign_object_dataset
    # if self.dependent?
    #   @reversed = true
    # end
  end


  # --------- Calculation Flow -------------------------------------------------

public

  # TODO: Rename to calculated_by_input and calculated_by_output
  #
  def calculated_by_left?
    !calculated_by_right?
  end

  def calculated_by_right?
    (dependent? or inversed_flexible? or ((constant? and self.share.nil?) == true)) || reversed?
  end

  # Does link have min-/max_demand? 
  # Important to figure out for which flexible links to calculate first.
  #
  def max_boundaries?
    flexible? && max_demand
  end


  # --------- Calculation ------------------------------------------------------

  def calculate
    if self.calculated != true
      self.value = self.send("calculate_#{@link_type}")
      self.calculated = true
    end
    self.value
  end

  # Updates the shares according to demand.
  # This is needed so that wouter_dances work correctly.
  #
  def update_share
    demand = input_external_demand
    if self.value and demand and demand > 0
      self.share = self.value / demand
    elsif value == 0.0
      # if the value is 0.0, we have to set rules what links
      # get what shares. In order to have wouter_dances work properly.
      self.share = 0.0 if constant?
      self.share = 1.0 if flexible?
    end
  end
  
protected
  # --------- Calculation -----------------------------------------------------


  # If share is set to NIL, take the parent converter demand
  #
  def calculate_constant
    if self.share.nil?
      val = output_external_demand
      raise "Constant Link with share = nil expects a demand of child converter #{@child.full_key}" if val.nil?
      val
    else
      self.share
    end
  end

  def calculate_dependent
    #calculate_flexible
    output_external_demand
  end

  def calculate_share
    share * input_external_demand
  end


  # Total converter demand - SUM(outputs.external_link_value)
  # we take the external_link_values because slots that have
  # inversed_flexible links are dynamic. So they do cannot have
  # fixed conversions (and thus no valid internal_link_values).
  #
  #
  def calculate_inversed_flexible
    #raise "#{@child.full_key} has no demand" if @child.demand.nil?
    @child.demand - @child.outputs.map(&:external_link_value).compact.sum
  end

  #
  #
  def calculate_flexible
    total_demand = input_external_demand
    return nil if total_demand.nil?

    inp = input
    already_supplied_demand = (inp and inp.external_value) || 0.0
    new_value = total_demand - already_supplied_demand

    apply_boundaries(new_value)
  end


  # Make sure that the value is greater than the min_value and smaller
  # than the max_value. 
  #
  # @param value [Float] value
  # @return [0.0,Float]
  #<commit_msg>make inversed_flexible not become negative. Fixes #194<commit_after>module Qernel
##
#
#
class Link
  extend ActiveModel::Naming
  include Topology::Link

  # --------- Dataset ---------------------------------------------------------

  include DatasetAttributes

  DATASET_ATTRIBUTES = [
    :share, 
    :value, 
    :max_demand, 
    :calculated,
    :country_specific
  ]

  dataset_accessors DATASET_ATTRIBUTES


  # --------- Accessor ---------------------------------------------------------

  attr_accessor :graph, # needed for dataset
                :parent, # Parent is the converter to the left (towards useful demand)
                :child,  # Child is the converter to the right (towards useful primary demand)
                :carrier 

  attr_reader :id,
              :link_type


  # --------- Flow ------------------------------------------------------------

  attr_accessor :reversed
  attr_reader :is_loss

  alias reversed? reversed
  alias loss? is_loss


  # --------- Link Types ------------------------------------------------------

  attr_reader :is_share, :flexible, :dependent, :constant, :inversed_flexible

  alias share? is_share
  alias flexible? flexible
  alias dependent? dependent
  alias constant? constant
  alias inversed_flexible? inversed_flexible


  # --------- Initialize ------------------------------------------------------

  def initialize(id, input, output, carrier, link_type, reversed = false)
    @id = id.is_a?(Numeric) ? id : Hashpipe.hash(id)

    @reversed = reversed
    @parent, @child, @carrier = input, output, carrier
    
    self.link_type = link_type.to_sym

    connect
    memoize_for_cache
  end

protected

  def connect
    @parent.add_input_link(self) if @parent # only used in testing
    @child.add_output_link(self) if @child  # only used in testing
  end

  def memoize_for_cache
    @is_loss = @carrier.andand.id == 1

    self.dataset_key # memoize dataset_key
  end

  def link_type=(link_type)
    @link_type = link_type

    @is_share = @link_type === :share
    @flexible = @link_type === :flexible
    @inversed_flexible = @link_type === :inversed_flexible
    @dependent = @link_type === :dependent
    @constant = @link_type === :constant
  end

  def after_assign_object_dataset
    # if self.dependent?
    #   @reversed = true
    # end
  end


  # --------- Calculation Flow -------------------------------------------------

public

  # TODO: Rename to calculated_by_input and calculated_by_output
  #
  def calculated_by_left?
    !calculated_by_right?
  end

  def calculated_by_right?
    (dependent? or inversed_flexible? or ((constant? and self.share.nil?) == true)) || reversed?
  end

  # Does link have min-/max_demand? 
  # Important to figure out for which flexible links to calculate first.
  #
  def max_boundaries?
    flexible? && max_demand
  end


  # --------- Calculation ------------------------------------------------------

  def calculate
    if self.calculated != true
      self.value = self.send("calculate_#{@link_type}")
      self.calculated = true
    end
    self.value
  end

  # Updates the shares according to demand.
  # This is needed so that wouter_dances work correctly.
  #
  def update_share
    demand = input_external_demand
    if self.value and demand and demand > 0
      self.share = self.value / demand
    elsif value == 0.0
      # if the value is 0.0, we have to set rules what links
      # get what shares. In order to have wouter_dances work properly.
      self.share = 0.0 if constant?
      self.share = 1.0 if flexible?
    end
  end
  
protected
  # --------- Calculation -----------------------------------------------------


  # If share is set to NIL, take the parent converter demand
  #
  def calculate_constant
    if self.share.nil?
      val = output_external_demand
      raise "Constant Link with share = nil expects a demand of child converter #{@child.full_key}" if val.nil?
      val
    else
      self.share
    end
  end

  def calculate_dependent
    #calculate_flexible
    output_external_demand
  end

  def calculate_share
    share * input_external_demand
  end


  # Total converter demand - SUM(outputs.external_link_value)
  # we take the external_link_values because slots that have
  # inversed_flexible links are dynamic. So they do cannot have
  # fixed conversions (and thus no valid internal_link_values).
  #
  # Inversed Flexible shouldn't become negative.
  # https://github.com/dennisschoenmakers/etengine/issues/194
  # 
  def calculate_inversed_flexible
    #raise "#{@child.full_key} has no demand" if @child.demand.nil?
    result = @child.demand - @child.outputs.map(&:external_link_value).compact.sum
    (result < 0.0) ? 0.0 : result
  end

  #
  #
  def calculate_flexible
    total_demand = input_external_demand
    return nil if total_demand.nil?

    inp = input
    already_supplied_demand = (inp and inp.external_value) || 0.0
    new_value = total_demand - already_supplied_demand

    apply_boundaries(new_value)
  end


  # Make sure that the value is greater than the min_value and smaller
  # than the max_value. 
  #
  # @param value [Float] value
  # @return [0.0,Float]
  #
{'fraction_non_alphanumeric': 0.16869838773102636, 'fraction_numerical': 0.0037357451828548957, 'mean_word_length': 2.0567908653846154, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 5, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3482698', 'n_tokens_mistral': 3106, 'n_tokens_neox': 2862, 'n_words': 855}