Class: Operation

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
DataAssociator, FieldValuer, OperationPlanner
Defined in:
app/models/operation.rb

Overview

Class that represents an operation in the lab Some very important methods include #input, #output, OperationStatus#error, #pass

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DataAssociator

#append_notes, #associate, #associations, #data_associations, #get, #get_association, #lazy_associate, #modify, #notes, #notes=, #upload

Class Method Details

.step(ops = nil) ⇒ Object



337
338
339
340
341
342
# File 'app/models/operation.rb', line 337

def self.step(ops = nil)
  ops ||= Operation.includes(:operation_type)
                   .where("status = 'waiting' OR status = 'deferred' OR status = 'delayed' OR status = 'pending'")

  ops.each(&:step)
end

Instance Method Details

#activateObject



317
318
319
320
321
322
323
324
325
# File 'app/models/operation.rb', line 317

def activate
  set_status 'planning'
  outputs.each do |output|
    output.wires_as_source.each do |wire|
      wire.active = true
      wire.save
    end
  end
end

#add_input(name, sample, container) ⇒ Object

Adds a new input to an operation, even if that operation doesn't specify the input in its definition. Useful for example when an operation determines which enzymes it will use once launched.

Examples:

Add input for items to discard

items.each do |i|
  items_in_inputs = op.inputs.map { |input| input.item }.uniq

  if not items_in_inputs.include? i
    n = "Discard Item #{i.id}"
    op.add_input n, i.sample, i.object_type
    op.input(n).set item: i
  end
end

Parameters:



127
128
129
130
131
132
133
134
135
# File 'app/models/operation.rb', line 127

def add_input(name, sample, container)
  items = Item.where(sample_id: sample.id, object_type_id: container.id).reject(&:deleted?)
  return nil if items.empty?

  item = items.first
  create_input(name: name, item: item, sample: sample)

  item
end

#add_successor(opts) ⇒ Object



414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'app/models/operation.rb', line 414

def add_successor(opts)
  ot = OperationType.find_by(name: opts[:type])

  op = ot.create_operation(
    status: 'waiting',
    user_id: user_id
  )

  plan.plan_associations.create(operation_id: op.id)

  opts[:routing].each do |r|
    ot.field_types.select { |ft| ft.routing == r[:symbol] }.each do |ft|
      aft = ft.allowable_field_types[0]
      op.set_property(ft.name, r[:sample], ft.role, false, aft)
    end
  end

  raise "Could not find output #{opts[:from]} of #{operation_type.name}" unless output(opts[:from])
  raise "Could not find input #{opts[:to]} of #{opts[:type]} (inputs = #{op.field_values.inspect})" unless op.input(opts[:to])

  wire = Wire.new(
    from_id: output(opts[:from]).id,
    to_id: op.input(opts[:to]).id,
    active: true
  )

  wire.save
end

#create_field_type(name) ⇒ Object

TODO: this belongs elsewhere



146
147
148
149
150
151
152
153
# File 'app/models/operation.rb', line 146

def create_field_type(name)
  FieldType.new(
    name: name,
    ftype: 'sample',
    parent_class: 'OperationType',
    parent_id: nil
  )
end

#create_field_value(name, item, sample, field_type) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
# File 'app/models/operation.rb', line 155

def create_field_value(name, item, sample, field_type)
  FieldValue.new(
    name: name,
    child_item_id: item.id,
    child_sample_id: sample.id,
    role: 'input',
    parent_class: 'Operation',
    parent_id: id,
    field_type_id: field_type.id
  )
end

#create_input(name:, item:, sample:) ⇒ Object



137
138
139
140
141
142
143
# File 'app/models/operation.rb', line 137

def create_input(name:, item:, sample:)
  field_type = create_field_type(name)
  field_type.save

  field_value = create_field_value(name, item, sample, field_type)
  field_value.save
end

#deactivateObject



327
328
329
330
331
332
333
334
335
# File 'app/models/operation.rb', line 327

def deactivate
  set_status 'unplanned'
  outputs.each do |output|
    output.wires_as_source.each do |wire|
      wire.active = false
      wire.save
    end
  end
end

#destroy_field_valuesObject



27
28
29
30
31
32
33
34
35
36
37
38
# File 'app/models/operation.rb', line 27

def destroy_field_values
  msg = "Cannot destroy operation #{id} because it has jobs associated with it"
  raise msg unless JobAssociation.where(operation_id: id).empty?

  fvs = FieldValue.where(parent_class: 'Operation', parent_id: id)
  fvs.each do |fv|
    Wire.where("from_id = #{fv.id} OR to_id = #{fv.id}").each do |wire|
      wire.destroy
    end
    fv.destroy
  end
end

#find(name) ⇒ Object



253
254
255
256
257
258
259
# File 'app/models/operation.rb', line 253

def find(name)
  ops = []
  recurse do |op|
    ops << op if op.operation_type.name == name
  end
  ops
end

#get_field_value(name, role = 'input') ⇒ FieldValue

Parameters:

  • name (String)
  • role (String) (defaults to: 'input')

Returns:



218
219
220
# File 'app/models/operation.rb', line 218

def get_field_value(name, role = 'input')
  field_values.find { |fv| fv.name == name && fv.role == role }
end

#get_input(name) ⇒ FieldValue

Parameters:

  • name (String)

Returns:



179
180
181
# File 'app/models/operation.rb', line 179

def get_input(name)
  inputs.find { |i| i.name == name }
end

#get_output(name) ⇒ FieldValue

Parameters:

  • name (String)

Returns:



185
186
187
# File 'app/models/operation.rb', line 185

def get_output(name)
  outputs.find { |o| o.name == name }
end

#input(name) ⇒ FieldValue

Parameters:

  • name (String)

Returns:



191
192
193
# File 'app/models/operation.rb', line 191

def input(name)
  get_input(name)
end

#input_array(name) ⇒ Array<FieldValue>

Inputs as Array extended with IOList

Parameters:

  • name (String)

Returns:



204
205
206
# File 'app/models/operation.rb', line 204

def input_array(name)
  inputs.select { |i| i.name == name }.extend(IOList)
end

#input_data(input_name, data_name) ⇒ Object



367
368
369
370
# File 'app/models/operation.rb', line 367

def input_data(input_name, data_name)
  fv = input(input_name)
  fv.child_data(data_name) if fv
end

#inputsArray<FieldValue>

Returns:



168
169
170
# File 'app/models/operation.rb', line 168

def inputs
  field_values.select { |ft| ft.role == 'input' }
end

#leaf?Boolean

STARTING PLANS

Returns:

  • (Boolean)


446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'app/models/operation.rb', line 446

def leaf?

  inputs.each do |i|
    next unless i.predecessors.count > 0

    i.predecessors.each do |pred|
      return pred.operation.leaf? if pred.operation.on_the_fly

      return false
    end
  end

  true
end

#nameString

Returns OperationType name.

Returns:

  • (String)

    OperationType name



49
# File 'app/models/operation.rb', line 49

delegate :name, to: :operation_type

#nominal_costObject



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'app/models/operation.rb', line 344

def nominal_cost
  return 0 unless operation_type.cost_model?

  begin
    operation_type.cost_model.load(binding: empty_binding)
  rescue ScriptError, StandardError => e
    raise "Error loading cost function for #{operation_type.name}: " + e.to_s
  end

  temp = status
  self.status = 'done'

  begin
    c = cost(self)
  rescue SystemStackError, ScriptError, StandardError => e
    self.status = temp
    raise "Error evaluating cost function for #{operation_type.name}: " + e.to_s
  end

  self.status = temp
  c
end

#on_the_flyBool

Returns Whether OperationType is on-the-fly.

Returns:

  • (Bool)

    Whether OperationType is on-the-fly



52
# File 'app/models/operation.rb', line 52

delegate :on_the_fly, to: :operation_type

#output(name) ⇒ FieldValue

Parameters:

  • name (String)

Returns:



197
198
199
# File 'app/models/operation.rb', line 197

def output(name)
  get_output name
end

#output_array(name) ⇒ Array<FieldValue>

Outputs as Array extended with IOList

Parameters:

  • name (String)

Returns:



211
212
213
# File 'app/models/operation.rb', line 211

def output_array(name)
  outputs.select { |o| o.name == name }.extend(IOList)
end

#output_data(output_name, data_name) ⇒ Object



372
373
374
375
# File 'app/models/operation.rb', line 372

def output_data(output_name, data_name)
  fv = output(output_name)
  fv.child_data(data_name) if fv
end

#outputsArray<FieldValue>

Returns:



173
174
175
# File 'app/models/operation.rb', line 173

def outputs
  field_values.select { |ft| ft.role == 'output' }
end

#parent_typeObject

interface with FieldValuer



40
41
42
# File 'app/models/operation.rb', line 40

def parent_type # interface with FieldValuer
  operation_type
end

#pass(input_name, output_name = nil) ⇒ Operation

Passes an input item to an output (alternative to Krill::OperationList#make)

Parameters:

  • input_name (String)
  • output_name (String) (defaults to: nil)

Returns:



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'app/models/operation.rb', line 226

def pass(input_name, output_name = nil)

  output_name ||= input_name

  fv_in = input(input_name)
  fv_out = output(output_name)

  raise "Could not find input '#{input_name}' in pass" unless fv_in
  raise "Could not find input '#{output_name}' in pass" unless fv_out

  fv_out.child_sample_id = fv_in.child_sample_id
  fv_out.child_item_id = fv_in.child_item_id
  fv_out.save

  self

end

#planPlan

Returns The plan that contains this Operation.

Returns:

  • (Plan)

    The plan that contains this Operation



55
56
57
# File 'app/models/operation.rb', line 55

def plan
  plans[0] unless plans.empty?
end

#precondition_valueObject



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'app/models/operation.rb', line 387

def precondition_value
  unless operation_type.precondition?
    message = "No precondition found for #{operation_type.name} (id: #{id})"
    Rails.logger.info message
    plan.associate 'Precondition load error', message
    return false
  end

  begin
    operation_type.precondition.load(binding: empty_binding)
  rescue ScriptError, StandardError => e
    # Raise "Error loading precondition for #{operation_type.name}: " + e.to_s
    Rails.logger.info "Error loading precondition for #{operation_type.name} (id: #{id})"
    plan.associate 'Precondition load error', e.message.to_s + ': ' + e.backtrace[0].to_s.sub('(eval)', 'line')
    return false
  end

  begin
    precondition(self)
  rescue SystemStackError, ScriptError, StandardError => e
    Rails.logger.info "PRECONDITION FOR OPERATION #{id} CRASHED"
    plan.associate 'Precondition Evaluation Error', e.message.to_s + ': ' + e.backtrace[0].to_s.sub('(eval)', 'line')
    false
  end

end

#predecessorsObject



285
286
287
288
289
290
291
292
293
294
# File 'app/models/operation.rb', line 285

def predecessors
  predecessor_list = []
  inputs.each do |input|
    input.predecessors.each do |predecessor|
      predecessor_list << predecessor.operation
    end
  end

  predecessor_list
end

#primed_predecessorsObject



296
297
298
299
300
301
302
303
304
305
# File 'app/models/operation.rb', line 296

def primed_predecessors
  ops = []
  inputs.each do |input|
    input.predecessors.each do |predecessor|
      ops << predecessor.operation if predecessor.operation && predecessor.operation.primed?
    end
  end

  ops
end

#recurse(&block) ⇒ Object



244
245
246
247
248
249
250
251
# File 'app/models/operation.rb', line 244

def recurse(&block)
  block.call(self)
  inputs.each do |input|
    input.predecessors.each do |predecessor|
      predecessor.operation.recurse(&block)
    end
  end
end

#set_input(name, val, aft = nil) ⇒ Object

Assigns a Sample to an input

Parameters:

  • name (String)
  • val (Sample, Item, Number)
  • aft (AllowableFieldType) (defaults to: nil)


99
100
101
# File 'app/models/operation.rb', line 99

def set_input(name, val, aft = nil)
  set_property(name, val, 'input', false, aft)
end

#set_input_data(input_name, data_name, value) ⇒ Object



377
378
379
380
# File 'app/models/operation.rb', line 377

def set_input_data(input_name, data_name, value)
  fv = input(input_name)
  fv.set_child_data(data_name, value) if fv
end

#set_output(name, val, aft = nil) ⇒ Object

Assigns a Sample to an output

Parameters:

  • name (String)
  • val (Sample)
  • aft (AllowableFieldType) (defaults to: nil)


107
108
109
# File 'app/models/operation.rb', line 107

def set_output(name, val, aft = nil)
  set_property(name, val, 'output', false, aft)
end

#set_output_data(output_name, data_name, value) ⇒ Object



382
383
384
385
# File 'app/models/operation.rb', line 382

def set_output_data(output_name, data_name, value)
  fv = output(output_name)
  fv.set_child_data(data_name, value) if fv
end

#set_status_recursively(str) ⇒ Object



261
262
263
264
265
266
# File 'app/models/operation.rb', line 261

def set_status_recursively(str)
  recurse do |op|
    op.status = str
    op.save
  end
end

#siblingsObject



307
308
309
310
311
312
313
314
315
# File 'app/models/operation.rb', line 307

def siblings
  ops = outputs.collect do |output|
    output.wires_as_source.collect do |wire|
      wire.to.predecessors.collect(&:operation)
    end
  end

  ops.flatten
end

#successorsObject



274
275
276
277
278
279
280
281
282
283
# File 'app/models/operation.rb', line 274

def successors
  successor_list = []
  outputs.each do |output|
    output.successors.each do |suc|
      successor_list << suc.operation
    end
  end

  successor_list
end

#temporaryObject



461
462
463
464
# File 'app/models/operation.rb', line 461

def temporary
  @temporary ||= {}
  @temporary
end

#to_sObject



268
269
270
271
272
# File 'app/models/operation.rb', line 268

def to_s
  ins = (inputs.collect { |fv| "#{fv.name}: #{fv.child_sample ? fv.child_sample.name : 'NO SAMPLE'}" }).join(', ')
  outs = (outputs.collect { |fv| "#{fv.name}: #{fv.child_sample ? fv.child_sample.name : 'NO SAMPLE'}" }).join(', ')
  "#{operation_type.name} #{id} ( " + ins + ' ) ==> ( ' + outs + ' )'
end

#virtual?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'app/models/operation.rb', line 44

def virtual?
  false
end

#with_input(name, sample) ⇒ Operation

Assigns a Sample to an input, choosing an appropriate allowable_field_type.

Parameters:

  • name (String)
  • sample (Sample)

Returns:



66
67
68
69
70
71
72
# File 'app/models/operation.rb', line 66

def with_input(name, sample)
  ft = operation_type.input(name)
  aft = ft.choose_aft_for(sample)
  set_input(name, sample, aft)

  self
end

#with_output(name, sample) ⇒ Object

Assigns a Sample to an output, choosing an appropriate allowable_field_type.

Parameters:

  • name (String)
  • sample (Sample)


78
79
80
81
82
83
84
# File 'app/models/operation.rb', line 78

def with_output(name, sample)
  ft = operation_type.output(name)
  aft = ft.choose_aft_for(sample)
  set_output(name, sample, aft)

  self
end

#with_property(name, value) ⇒ Object

Assigns a value to an input parameter

Parameters:

  • name (String)
  • value


89
90
91
# File 'app/models/operation.rb', line 89

def with_property(name, value)
  set_property(name, value, 'input', false, nil)
end