rulp 0.0.2 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MjEyMDI0ZDM1NzVhNWE3NmJkYTk5Yzg3ODQyMjRiMmYxOWFkMDdkZQ==
4
+ Yzk4NWFlMmYwMGQ0ZDFjZTUxMmEwNzlkNDAwZmQ4MDU3YWY2Zjg5Mg==
5
5
  data.tar.gz: !binary |-
6
- YzE0NmE1YzFhNDg1YTI4YjUzNTc3YmY2NzA4NTI3MDE1NGE2MzI2NA==
6
+ MDI3Y2E5MjdhNTY2M2FiZDBhZGMzZTg4MzU2ZDRiMzgyOGFmZTIzMA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- M2ZhODczODZkYmU4ZGQ0N2RmZGM4MzZiN2ZmYjc3NjhkZjAyOTE0MjE0MWNj
10
- ZTI1MWJkYzA5YWI0NmQyMjE1NmJjNTZhMGUxYjIyNDA4NzhjZDQzZTBkNzNi
11
- ZjE3ZjQwZjEyNDI4YWNmYTkwMDk5NzZkYjliYTIxMTczMGJlODY=
9
+ YTg0OTJlNjhiMGJkNjdlM2U1ZTQ0ZGMyMTkwMTc0ZmI5NjliZjIzM2M3NjE1
10
+ N2VlNTBiNDBjZmIwOTA5MWI2OGEzZjhkYzYyMmFkOTkzNWM5YTg5OWUzNTc3
11
+ ZmUwY2U0ODM0ZmJhNTdiNjY2M2JjZDNhNzI0MTJmZDllMDY4Y2Q=
12
12
  data.tar.gz: !binary |-
13
- ODhjYmYwOGY1N2E0MDc2OTlmYjBjZTRjN2VmMDk5NjVkOGIyMDM3NDA4ZTU2
14
- YWE0MWU3ODIwY2VhZmI4NWU0OTcwMDg0ZmJiMzIzNTk2OGIwZDQ2ZTUwMDFh
15
- ZTIwM2EwZDVmYWMyZjdkYzgwZDZiYWQyYmJmN2Y5ZjYzMjdmNWQ=
13
+ MzU4YjQxNzkzNTRlMmU0NzMyNTIyNGQ0NmE1ODM1YzU0NmYyYzFiZmM2YzI1
14
+ YmI2MTFkNWZmMzJjZWFmZjY0ZTAzODVmNTEwZTk5MDlkYjBhMmZhNzg1NzZm
15
+ NzFhNTllMTM1MTIwNmJmMDk3MGYyNTRmZGY5ZGMyM2U5YWI2ZjA=
@@ -1,3 +1,5 @@
1
1
  require_relative './array_extensions'
2
2
  require_relative './kernel_extensions'
3
- require_relative './object_extensions'
3
+ require_relative './object_extensions'
4
+ require_relative './file_extensions'
5
+ require_relative './os_extensions'
@@ -0,0 +1,9 @@
1
+ def choose_file
2
+ case os
3
+ when :macosx
4
+ command = "osascript -e 'set the_file to choose file name with prompt \"Select an output file\"' -e 'set the result to POSIX path of the_file'"
5
+ File.absolute_path(`#{command}`.strip)
6
+ else
7
+ File.absolute_path(gets)
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ def os
2
+ @os ||= (
3
+ require "rbconfig"
4
+ host_os = RbConfig::CONFIG['host_os'].downcase
5
+
6
+ case host_os
7
+ when /linux/
8
+ :linux
9
+ when /darwin|mac os/
10
+ :macosx
11
+ when /mswin|msys|mingw32/
12
+ :windows
13
+ when /cygwin/
14
+ :cygwin
15
+ when /solaris|sunos/
16
+ :solaris
17
+ when /bsd/
18
+ :bsd
19
+ when /aix/
20
+ :aix
21
+ else
22
+ raise Error, "unknown os: #{host_os.inspect}"
23
+ end
24
+ )
25
+ end
data/lib/helpers/log.rb CHANGED
@@ -26,7 +26,7 @@ module Rulp
26
26
  }
27
27
 
28
28
  def self.level=(value)
29
- raise Exception.new("#{value} is not a valid log level") unless LEVELS[value]
29
+ raise StandardError.new("#{value} is not a valid log level") unless LEVELS[value]
30
30
  @@level = value
31
31
  end
32
32
 
data/lib/rulp.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  STDOUT.sync = true
2
- require_relative 'rulp/rulp'
2
+ require_relative 'rulp/rulp'
@@ -12,6 +12,19 @@ class Constraint
12
12
  end
13
13
 
14
14
  def to_s
15
- return "#{@expressions} #{@constraint_op == :== ? "=" : @constraint_op} #{@value}"
15
+ return "#{@expressions} #{constraint_op} #{@value}"
16
+ end
17
+
18
+ def constraint_op
19
+ case "#{@constraint_op}"
20
+ when "=="
21
+ "="
22
+ when "<"
23
+ "<="
24
+ when ">"
25
+ ">="
26
+ else
27
+ @constraint_op
28
+ end
16
29
  end
17
30
  end
data/lib/rulp/lv.rb CHANGED
@@ -13,8 +13,8 @@ class LV
13
13
  ->(index){ send(self.meth(index)) }
14
14
  end
15
15
 
16
- def meth(index)
17
- "#{self.name}#{index}_#{self.suffix}"
16
+ def meth(*args)
17
+ "#{self.name}#{args.join("_")}_#{self.suffix}"
18
18
  end
19
19
 
20
20
  def suffix
@@ -31,7 +31,15 @@ class LV
31
31
 
32
32
  def self.definition(name)
33
33
  self.class.send(:define_method, name){
34
- LV::names_table["#{name}#{self}"] if LV::names_table["#{name}#{self}"].class == self
34
+ defined = LV::names_table["#{name}"]
35
+ if defined && defined.class != self
36
+ raise StandardError.new("ERROR:\n#{name} was already defined as a variable of type #{defined.class}."+
37
+ "You are trying to redefine it as a variable of type #{self}")
38
+ elsif(!defined)
39
+ self.new(name)
40
+ else
41
+ defined
42
+ end
35
43
  }
36
44
  return self.send(name) || self.new(name)
37
45
  end
@@ -54,12 +62,19 @@ class LV
54
62
  end
55
63
 
56
64
  def value
65
+ return nil unless @value
57
66
  if self.class == BV
58
67
  return @value.round(2) == 1
68
+ elsif self.class == IV
69
+ return @value
59
70
  else
60
71
  @value
61
72
  end
62
73
  end
74
+
75
+ def inspect
76
+ "#{name}(#{suffix})[#{value || 'undefined'}]"
77
+ end
63
78
  end
64
79
 
65
80
  class BV < LV;
data/lib/rulp/rulp.rb CHANGED
@@ -24,21 +24,21 @@ module Rulp
24
24
  CBC = ::CBC
25
25
 
26
26
  SOLVERS = {
27
- "glpsol" => Glpk,
28
- "scip" => Scip,
29
- "cbc" => Cbc
27
+ GLPK => Glpk,
28
+ SCIP => Scip,
29
+ CBC => Cbc
30
30
  }
31
31
 
32
32
  def self.Glpk(lp)
33
- lp.solve_with(GLPK)
33
+ lp.solve_with(GLPK) rescue nil
34
34
  end
35
35
 
36
36
  def self.Cbc(lp)
37
- lp.solve_with(CBC)
37
+ lp.solve_with(CBC) rescue nil
38
38
  end
39
39
 
40
40
  def self.Scip(lp)
41
- lp.solve_with(SCIP)
41
+ lp.solve_with(SCIP) rescue nil
42
42
  end
43
43
 
44
44
  def self.Max(objective_expression)
@@ -51,6 +51,12 @@ module Rulp
51
51
  Problem.new(Rulp::MIN, objective_expression)
52
52
  end
53
53
 
54
+ def self.solver_exists?(solver_name)
55
+ solver = solver_name[0].upcase + solver_name[1..-1].downcase
56
+ solver_class = ObjectSpace.const_defined?(solver) && ObjectSpace.const_get(solver)
57
+ solver_class.exists? if(solver_class)
58
+ end
59
+
54
60
  class Problem
55
61
  def initialize(objective, objective_expression)
56
62
  @objective = objective
@@ -66,10 +72,32 @@ module Rulp
66
72
  self
67
73
  end
68
74
 
75
+ def solve
76
+ Rulp.send(self.solver, self)
77
+ end
78
+
79
+ def method_missing(method_name)
80
+ self.call(method_name)
81
+ end
82
+
83
+ def solver(solver=nil)
84
+ solver = solver || ENV["SOLVER"] || "Scip"
85
+ solver = solver[0].upcase + solver[1..-1].downcase
86
+ end
87
+
88
+ def call(using=nil)
89
+ Rulp.send(self.solver(using), self)
90
+ end
91
+
69
92
  def constraints
70
- @constraints.map.with_index{|constraint, i|
93
+ constraints_str = @constraints.map.with_index{|constraint, i|
71
94
  " c#{i}: #{constraint}"
72
- }.join("\n")
95
+ }.join("\n").strip
96
+ if constraints_str.empty?
97
+ "0 #{@variables.first} = 0"
98
+ else
99
+ " #{constraints_str}"
100
+ end
73
101
  end
74
102
 
75
103
  def integers
@@ -84,8 +112,8 @@ module Rulp
84
112
 
85
113
  def bounds
86
114
  @variables.map{|var|
87
- next unless var.bounds_str
88
- " #{var.bounds_str}"
115
+ next unless var.bounds
116
+ " #{var.bounds}"
89
117
  }.compact.join("\n")
90
118
  end
91
119
 
@@ -93,7 +121,7 @@ module Rulp
93
121
  "/tmp/rulp-#{Random.rand(0..1000)}.lp"
94
122
  end
95
123
 
96
- def output(filename)
124
+ def output(filename=choose_file)
97
125
  IO.write(filename, self)
98
126
  end
99
127
 
@@ -120,15 +148,22 @@ module Rulp
120
148
  return result
121
149
  end
122
150
 
151
+ def inspect
152
+ to_s
153
+ end
154
+
123
155
  def to_s
124
- "\
125
- #{@objective}
126
- obj: #{@objective_expression}
127
- Subject to
128
- #{constraints}
129
- Bounds
130
- #{bounds}#{integers}#{bits}
131
- End"
156
+ %Q(
157
+ #{' '*0}#{@objective}
158
+ #{' '*0} obj: #{@objective_expression}
159
+ #{' '*0}Subject to
160
+ #{' '*0}#{constraints}
161
+ #{' '*0}Bounds
162
+ #{' '*0}#{bounds}#{integers}#{bits}
163
+ #{' '*0}End
164
+ )
132
165
  end
166
+
167
+ alias_method :save, :output
133
168
  end
134
169
  end
@@ -49,7 +49,7 @@ module Rulp
49
49
  return self, something
50
50
  end
51
51
 
52
- def bounds_str
52
+ def bounds
53
53
  return nil if !(self.gt || self.lt || self.const)
54
54
  return "#{self.name} = #{self.const}" if self.const
55
55
 
@@ -1,8 +1,8 @@
1
1
  module Rulp
2
2
  module Initializers
3
3
  def initialize(name)
4
- raise Exception.new("Variable with the name #{name} of a different type (#{LV::names_table[name].class}) already exists") if LV::names_table[name]
5
- LV::names_table["#{name}#{self.class}"] = self
4
+ raise StandardError.new("Variable with the name #{name} of a different type (#{LV::names_table[name].class}) already exists") if LV::names_table["#{name}"]
5
+ LV::names_table["#{name}"] = self
6
6
  @name = name
7
7
  end
8
8
 
@@ -14,6 +14,10 @@ module Rulp
14
14
  def names_table
15
15
  @@names ||= {}
16
16
  end
17
+
18
+ def clear
19
+ @@names = {}
20
+ end
17
21
  end
18
22
 
19
23
  def to_s
data/lib/solvers/cbc.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  class Cbc < Solver
2
2
  def solve(open_solution=false)
3
- `#{executable} #{@filename} branch solution #{@outfile}`
3
+ system("#{executable} #{@filename} branch solution #{@outfile}")
4
4
  `open #{@outfile}` if open_solution
5
5
  end
6
6
 
7
- def executable
7
+ def self.executable
8
8
  :cbc
9
9
  end
10
10
 
data/lib/solvers/glpk.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  class Glpk < Solver
2
2
  def solve(open_solution=false)
3
- `#{executable} --lp #{@filename} --write #{@outfile}`
3
+ system("#{executable} --lp #{@filename} --write #{@outfile}")
4
4
  `open #{@outfile}` if open_solution
5
5
  end
6
6
 
7
- def executable
7
+ def self.executable
8
8
  :glpsol
9
9
  end
10
10
 
data/lib/solvers/scip.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  class Scip < Solver
2
- def solve(open_solution=false)
3
- `#{executable} -f #{@filename} > #{@outfile}`
4
- `open #{@outfile}` if open_solution
5
- end
6
2
 
7
- def executable
3
+ def self.executable
8
4
  :scip
9
5
  end
10
6
 
7
+ def solve(open_solution=false)
8
+ system("rm #{@outfile}; #{executable} -f #{@filename} -l #{@outfile}")
9
+ `open #{@outfile}` if open_solution
10
+ end
11
+
11
12
  def store_results(variables)
12
13
  results = IO.read(@outfile)
13
14
  start = results.sub(/.*?primal solution:.*?=+/m, "")
@@ -2,7 +2,7 @@ class Solver
2
2
  def initialize(filename)
3
3
  @filename = filename
4
4
  @outfile = "/tmp/#{executable}-output.txt"
5
- raise Exception.new("Couldn't find solver #{executable}!") if `which #{executable}`.length == 0
5
+ raise StandardError.new("Couldn't find solver #{executable}!") if `which #{executable}`.length == 0
6
6
  @solver_exists = true
7
7
  end
8
8
 
@@ -10,9 +10,17 @@ class Solver
10
10
  puts "Not yet implemented"
11
11
  end
12
12
 
13
+ def executable
14
+ self.class.executable
15
+ end
16
+
13
17
  def solver_exists?
14
18
  @solver_exists || false
15
19
  end
20
+
21
+ def self.exists?
22
+ return `which #{self.executable}`.length != 0
23
+ end
16
24
  end
17
25
 
18
26
  require_relative 'cbc'
@@ -0,0 +1,58 @@
1
+ require_relative 'test_helper'
2
+
3
+ class BasicSuite < Minitest::Test
4
+
5
+ def test_single_binary_var
6
+ each_solver do |solver|
7
+ assert_equal X_b.value, nil
8
+
9
+ # The minimal value for a single binary variable is 0
10
+ Rulp::Min(X_b).(solver)
11
+ assert_equal X_b.value, false
12
+
13
+ # The maximal value for a single binary variable is 1
14
+ Rulp::Max(X_b).(solver)
15
+ assert_equal X_b.value, true
16
+
17
+ # If we set an upper bound this is respected by the solver
18
+ Rulp::Max(X_b)[1 * X_b <= 0].(solver)
19
+ assert_equal X_b.value, false
20
+
21
+ # If we set a lower bound this is respected by the solver
22
+ Rulp::Min(X_b)[1 * X_b >= 1].(solver)
23
+ assert_equal X_b.value, true
24
+ end
25
+ end
26
+
27
+ def test_single_integer_var
28
+ each_solver do |solver|
29
+ assert_equal X_i.value, nil
30
+
31
+ given[ -35 <= X_i <= 35 ]
32
+
33
+ # Integer variables respect integer bounds
34
+ Rulp::Min(X_i).(solver)
35
+ assert_equal X_i.value, -35
36
+
37
+ # Integer variables respect integer bounds
38
+ Rulp::Max(X_i).(solver)
39
+ assert_equal X_i.value, 35
40
+ end
41
+ end
42
+
43
+ def test_single_general_var
44
+ each_solver do |solver|
45
+ assert_equal X_f.value, nil
46
+
47
+ given[ -345.4321 <= X_f <= 345.4321 ]
48
+
49
+ # Integer variables respect integer bounds
50
+ Rulp::Min(X_f).(solver)
51
+ assert_equal X_f.value, -345.4321
52
+
53
+ # Integer variables respect integer bounds
54
+ Rulp::Max(X_f).(solver)
55
+ assert_equal X_f.value, 345.4321
56
+ end
57
+ end
58
+ end
data/test/test_boolean.rb CHANGED
@@ -9,36 +9,23 @@ class BooleanTest < Minitest::Test
9
9
  def setup
10
10
  @items = 30.times.map(&Shop_Item_b)
11
11
  items_count = @items.sum
12
- items_costs = @items.map{|item| item * Random.rand(1.0...5.0)}.sum
12
+ @items_costs = @items.map{|item| item * Random.rand(1.0...5.0)}.sum
13
13
 
14
14
  @problem =
15
- Rulp::Min( items_costs ) [
15
+ Rulp::Min( @items_costs ) [
16
16
  items_count >= 10,
17
- items_costs >= 15
17
+ @items_costs >= 15
18
18
  ]
19
19
  end
20
20
 
21
- def test_scip
22
- solution = Rulp::Scip @problem
23
- selected = @items.select(&:value)
24
- assert_equal selected.length, 10
25
- assert_operator solution.round(2), :>=, 15
26
- assert_operator solution, :<=, 25
27
- end
28
-
29
- def test_cbc
30
- solution = Rulp::Cbc @problem
31
- selected = @items.select(&:value)
32
- assert_equal selected.length, 10
33
- assert_operator solution.round(2), :>=, 15
34
- assert_operator solution, :<=, 25
35
- end
36
-
37
- def test_glpk
38
- solution = Rulp::Glpk @problem
39
- selected = @items.select(&:value)
40
- assert_equal selected.length, 10
41
- assert_operator solution.round(2), :>=, 15
42
- assert_operator solution, :<=, 25
21
+ def test_simple
22
+ each_solver do |solver|
23
+ setup
24
+ @problem.send(solver)
25
+ selected = @items.select(&:value)
26
+ assert_equal selected.length, 10
27
+ assert_operator @items_costs.evaluate.round(2), :>=, 15
28
+ assert_operator @items_costs.evaluate, :<=, 25
29
+ end
43
30
  end
44
31
  end
data/test/test_helper.rb CHANGED
@@ -1,6 +1,18 @@
1
- gem "minitest"
2
-
3
1
  require_relative "../lib/rulp"
2
+
3
+ gem "minitest"
4
4
  require "minitest/autorun"
5
5
 
6
6
  Rulp::Logger::level = :off
7
+
8
+
9
+ def each_solver
10
+ [:scip, :cbc, :glpk, :gurobi].each do |solver|
11
+ LV::clear
12
+ if Rulp::solver_exists?(solver)
13
+ yield(solver)
14
+ else
15
+ puts "Couldn't find solver #{solver}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'test_helper'
2
+ # maximize
3
+ # z = 10 * x + 6 * y + 4 * z
4
+ #
5
+ # subject to
6
+ # p: x + y + z <= 100
7
+ # q: 10 * x + 4 * y + 5 * z <= 600
8
+ # r: 2 * x + 2 * y + 6 * z <= 300
9
+ #
10
+ # where all variables are non-negative integers
11
+ # x >= 0, y >= 0, z >= 0
12
+ #
13
+
14
+ class SaveToFile < Minitest::Test
15
+ def setup
16
+ given[ X_i >= 0, Y_i >= 0, Z_i >= 0 ]
17
+ @objective = 10 * X_i + 6 * Y_i + 4 * Z_i
18
+ @problem = Rulp::Max( @objective ) [
19
+ X_i + Y_i + Z_i <= 100,
20
+ 10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
21
+ 2 * X_i + 2 * Y_i + 6 * Z_i <= 300
22
+ ]
23
+ end
24
+
25
+ def test_save
26
+ sample_output_filename = @problem.get_output_filename
27
+ @problem.save(sample_output_filename)
28
+ assert_equal(IO.read(sample_output_filename), "#{@problem}")
29
+ assert_operator "#{@problem}".length, :>=, 100
30
+ end
31
+ end
data/test/test_simple.rb CHANGED
@@ -14,37 +14,22 @@ require_relative 'test_helper'
14
14
  class SimpleTest < Minitest::Test
15
15
  def setup
16
16
  given[ X_i >= 0, Y_i >= 0, Z_i >= 0 ]
17
- @problem =
18
- Rulp::Max( 10 * X_i + 6 * Y_i + 4 * Z_i ) [
17
+ @objective = 10 * X_i + 6 * Y_i + 4 * Z_i
18
+ @problem = Rulp::Max( @objective ) [
19
19
  X_i + Y_i + Z_i <= 100,
20
20
  10 * X_i + 4 * Y_i + 5 * Z_i <= 600,
21
21
  2 * X_i + 2 * Y_i + 6 * Z_i <= 300
22
22
  ]
23
23
  end
24
24
 
25
- def test_scip
26
- solution = Rulp::Scip @problem
27
- assert_equal X_i.value, 33
28
- assert_equal Y_i.value, 67
29
- assert_equal Z_i.value, 0
30
- assert_equal solution , 732
31
-
32
- end
33
-
34
- def test_cbc
35
- solution = Rulp::Cbc @problem
36
- assert_equal X_i.value, 33
37
- assert_equal Y_i.value, 67
38
- assert_equal Z_i.value, 0
39
- assert_equal solution , 732
40
-
41
- end
42
-
43
- def test_glpk
44
- solution = Rulp::Glpk @problem
45
- assert_equal X_i.value, 33
46
- assert_equal Y_i.value, 67
47
- assert_equal Z_i.value, 0
48
- assert_equal solution , 732
25
+ def test_simple
26
+ each_solver do |solver|
27
+ setup
28
+ @problem.send(solver)
29
+ assert_equal X_i.value, 33
30
+ assert_equal Y_i.value, 67
31
+ assert_equal Z_i.value, 0
32
+ assert_equal @objective.evaluate , 732
33
+ end
49
34
  end
50
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rulp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-18 00:00:00.000000000 Z
11
+ date: 2015-03-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple Ruby LP description DSL
14
14
  email: wc@pico.net.nz
@@ -20,8 +20,10 @@ files:
20
20
  - bin/rulp
21
21
  - lib/extensions/array_extensions.rb
22
22
  - lib/extensions/extensions.rb
23
+ - lib/extensions/file_extensions.rb
23
24
  - lib/extensions/kernel_extensions.rb
24
25
  - lib/extensions/object_extensions.rb
26
+ - lib/extensions/os_extensions.rb
25
27
  - lib/helpers/log.rb
26
28
  - lib/rulp.rb
27
29
  - lib/rulp/constraint.rb
@@ -34,8 +36,10 @@ files:
34
36
  - lib/solvers/glpk.rb
35
37
  - lib/solvers/scip.rb
36
38
  - lib/solvers/solvers.rb
39
+ - test/test_basic_suite.rb
37
40
  - test/test_boolean.rb
38
41
  - test/test_helper.rb
42
+ - test/test_save_to_file.rb
39
43
  - test/test_simple.rb
40
44
  homepage:
41
45
  licenses: []
@@ -61,7 +65,9 @@ signing_key:
61
65
  specification_version: 4
62
66
  summary: Ruby Linear Programming
63
67
  test_files:
68
+ - test/test_basic_suite.rb
64
69
  - test/test_boolean.rb
65
70
  - test/test_helper.rb
71
+ - test/test_save_to_file.rb
66
72
  - test/test_simple.rb
67
73
  has_rdoc: