ballistics-ng 0.1.0.1 → 0.1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6cab378877c914007cb15e44240be0256b9bc837
4
- data.tar.gz: e764833a574ad53b621db05e3a876a436c15f667
3
+ metadata.gz: 03d24f50b7132229db03bb1eb7873a7828ed8858
4
+ data.tar.gz: 43cd6e8398d7f346ed6f0226b304a1dd000f014a
5
5
  SHA512:
6
- metadata.gz: 33685521c47161c77bdbdea0cb6edc597b30f721b3723efba9b5f6ed0e8ee9959fa2ba619e4e27d62cb041a1dd1f52ccf7ed1c20367085b451a288ddbe9b7ff0
7
- data.tar.gz: a167d4e4de9c66ee10feba372b65ce83629d9922a8c3eeccb686d5c4716840f4a8be6d947f6027ec776d524d1cefb9f6281cf67a171ed26c50a6f3c40f63faf1
6
+ metadata.gz: 2d13c3f18c4798d23088eadd755ce1685e2301f52449e7ae3a3f8b05acc2b5a0272006c2e3a6ac80c68e4b1f3a9e972a6efc27602987923469405c780b491a50
7
+ data.tar.gz: 58d7e56498841d05dc2129c2a128b753690f7e722c302b96dfa1d51a985320e433f143d35184bb687af25b82099947870049c49a4b988d19291eabe5c9ad07d8
data/README.md CHANGED
@@ -1,12 +1,112 @@
1
+ [![Build Status](https://travis-ci.org/rickhull/ballistics.svg)](https://travis-ci.org/rickhull/ballistics)
2
+ [![Gem Version](https://badge.fury.io/rb/ballistics-ng.svg)](http://badge.fury.io/rb/ballistics-ng)
3
+ [![Dependency Status](https://gemnasium.com/rickhull/ballistics.svg)](https://gemnasium.com/rickhull/ballistics)
4
+ [![Security Status](https://hakiri.io/github/rickhull/ballistics/master.svg)](https://hakiri.io/github/rickhull/ballistics/master)
5
+
6
+ # Ballistics-NG (next gen)
7
+
8
+ This gem consists of a C extension which wraps the "GNU Ballistics" C library
9
+ (which is not an official GNU project as far as I can tell) and some
10
+ additional Ruby code for managing input data.
11
+
12
+ Feed in projectile and atmospheric specifics in order to retrieve trajectory
13
+ information at range.
14
+
15
+ This project is originally based on the **ballistics** gem, which has been
16
+ abandoned since 2013. It is anticipated that this gem will take over the
17
+ **ballistics** name, but until then, this gem is known as **ballistics-ng**
18
+
1
19
  # Install
2
20
 
3
21
  ```
4
- # Note, this project is based on an abandoned gem and has not settled on
5
- # a gem name yet
22
+ $ gem install ballistics-ng
23
+ ```
24
+
25
+ # Usage
26
+
27
+ ```ruby
28
+ require 'ballistics/problem'
6
29
 
7
- # So first, clone this repo; then:
30
+ prob = Ballistics::Problem.simple(gun_family: 'rifles',
31
+ gun_id: 'ar15_300_blk',
32
+ cart_id: 'barnes_110_vor_tx')
8
33
 
9
- cd ballistics
10
- gem install rake-compiler
11
- rake rebuild
34
+ puts prob.report
35
+ puts
36
+ puts
37
+ puts prob.table
12
38
  ```
39
+
40
+ ```
41
+ $ ruby -Ilib examples/table.rb
42
+
43
+ GUN: AR-15 with 10.5 inch barrel (300 BLK)
44
+ ===
45
+ Chamber: 300 BLK
46
+ Barrel length: 10.5
47
+ Sight height: 2.6
48
+ Zero Range: 50
49
+
50
+ CARTRIDGE: Barnes 300 BLK 110gr VOR-TX
51
+ =========
52
+ Case: 300 BLK
53
+ MV @ 10: 2180
54
+ MV @ 16: 2350
55
+ Desc: Hunting round for penetration and expansion
56
+
57
+ PROJECTILE: Barnes 110gr TAC-TX 300 BLK
58
+ ==========
59
+ Caliber: 0.308
60
+ Grains: 110
61
+ BC (G1): 0.289
62
+ Desc: Black polymer tip, copper bullet
63
+
64
+
65
+ Range Time FPS Path
66
+ 0 0.000 2180.0 -2.6
67
+ 50 0.072 2043.1 -0.0
68
+ 100 0.147 1911.8 0.5
69
+ 150 0.229 1786.0 -1.4
70
+ 200 0.316 1666.5 -6.0
71
+ 250 0.409 1553.7 -13.7
72
+ 300 0.509 1448.3 -25.1
73
+ 350 0.616 1351.2 -40.6
74
+ 400 0.731 1263.9 -60.9
75
+ 450 0.854 1187.7 -86.6
76
+ 500 0.983 1123.6 -118.5
77
+ ```
78
+
79
+ # Features
80
+
81
+ ## `Ballistics::Problem`
82
+
83
+ * specify a gun and cartridge (etc) for meaningful results
84
+
85
+ ## `Ballistics::Atmosphere`
86
+
87
+ * specify altitude, humidity, pressure, and temp
88
+ * Army and ICAO atmospheres included at no charge
89
+
90
+ ## `Ballistics::Gun`
91
+
92
+ * determines sight height, zero angle, chamber,
93
+ muzzle velocity per barrel length
94
+ * determines the cartridge by chamber
95
+
96
+ ## `Ballistics::Cartridge`
97
+
98
+ * organized by chamber (e.g. *300 BLK*)
99
+ * *projectile* (see below)
100
+ * *case* (determines the chamber)
101
+ * optionally: *powder charge* (and *primer*) details
102
+
103
+ ## `Ballistics::Projectile`
104
+
105
+ * organized by chamber
106
+ * determines caliber, grains, ballistic coefficient, and drag function
107
+
108
+ ## Built In YAML Components
109
+
110
+ * [guns](https://github.com/rickhull/ballistics/tree/master/lib/ballistics/guns)
111
+ * [cartridges](https://github.com/rickhull/ballistics/tree/master/lib/ballistics/cartridges)
112
+ * [projectiles](https://github.com/rickhull/ballistics/tree/master/lib/ballistics/projectiles)
data/Rakefile CHANGED
@@ -1,19 +1,26 @@
1
- begin
2
- require "rake/testtask"
1
+ require "rake/testtask"
3
2
 
4
- # add test task
5
- Rake::TestTask.new do |t|
6
- t.test_files = FileList['test/**/*.rb']
7
- end
8
- desc "Run minitest tests"
3
+ # add test task
4
+ Rake::TestTask.new do |t|
5
+ t.test_files = FileList['test/**/*.rb']
6
+ end
7
+ desc "Run minitest tests"
8
+
9
+ desc "Run example scripts"
10
+ task :examples do
11
+ Dir['examples/**/*.rb'].each { |fn|
12
+ puts
13
+ sh "ruby -Ilib #{fn}"
14
+ puts
15
+ }
16
+ end
9
17
 
10
- task default: :test
18
+ task default: [:test, :examples]
19
+
20
+ #
21
+ # C EXTENSION
22
+ #
11
23
 
12
- @test_task = true
13
- rescue Exception => e
14
- warn "testtask error: #{e}"
15
- @test_task = false
16
- end
17
24
 
18
25
  begin
19
26
  gem "rake-compiler"
@@ -37,6 +44,11 @@ rescue Exception => e
37
44
  warn "rake-compiler error: #{e}"
38
45
  end
39
46
 
47
+ #
48
+ # GEM BUILD / PUBLISH
49
+ #
50
+
51
+
40
52
  begin
41
53
  require 'buildar'
42
54
 
@@ -1,5 +1,5 @@
1
1
  #include <ruby.h>
2
- #include <gnu_ballistics.h>
2
+ #include "include/gnu_ballistics.h"
3
3
 
4
4
  VALUE method_zero_angle(VALUE self,
5
5
  VALUE drag_function,
@@ -1,6 +1,8 @@
1
1
  require 'ballistics/yaml'
2
2
 
3
3
  class Ballistics::Cartridge
4
+ YAML_DIR = 'cartridges'
5
+
4
6
  MANDATORY = {
5
7
  "name" => :string,
6
8
  "case" => :string,
@@ -20,20 +22,14 @@ class Ballistics::Cartridge
20
22
  '.308' => 24,
21
23
  }
22
24
 
25
+ # 1% FPS gain/loss per inch of barrel length
26
+ FPS_INCH_FACTOR = 0.01
27
+
23
28
  # Load a built-in YAML file and instantiate cartridge objects
24
29
  # Return a hash of cartridge objects keyed by the cartridge id as in the YAML
25
30
  #
26
- def self.find(short_name)
27
- objects = {}
28
- Ballistics::YAML.load_built_in('cartridges', short_name).each { |id, hsh|
29
- obj = self.new(hsh)
30
- if block_given?
31
- objects[id] = obj if yield obj
32
- else
33
- objects[id] = obj
34
- end
35
- }
36
- objects
31
+ def self.find(file: nil, id: nil)
32
+ Ballistics::YAML.find(klass: self, file: file, id: id)
37
33
  end
38
34
 
39
35
  # This is a helper method to perform loading of cartridges and projectiles
@@ -43,9 +39,8 @@ class Ballistics::Cartridge
43
39
  #
44
40
  def self.find_with_projectiles(chamber)
45
41
  require 'ballistics/projectile'
46
-
47
- cartridges = self.find(chamber)
48
- projectiles = Ballistics::Projectile.find(chamber)
42
+ cartridges = self.find(file: chamber)
43
+ projectiles = Ballistics::Projectile.find(file: chamber)
49
44
  self.cross_ref(cartridges, projectiles)
50
45
  cartridges
51
46
  end
@@ -82,15 +77,70 @@ class Ballistics::Cartridge
82
77
  # and a known burn length
83
78
  # Guess a muzzle velocity for an unknown length
84
79
  #
85
- def self.guess_mv(known_length, known_mv, burn_length, unknown_length)
86
- inch_diff = known_length - unknown_length
87
- known_bf = burn_length.to_f / known_length
88
- unknown_bf = burn_length.to_f / unknown_length
89
- # assume 1% FPS per inch; adjust for burn_length and take the average
90
- fps_per_inch = known_mv * (known_bf + unknown_bf) / 2 / 100
80
+ def self.guess_mv(known_length, known_mv, barrel_length, burn_length = nil)
81
+ inch_diff = known_length - barrel_length
82
+ burn_factor = self.burn_factor(burn_length, known_length, barrel_length)
83
+ fps_per_inch = known_mv * burn_factor * FPS_INCH_FACTOR
91
84
  known_mv - inch_diff * fps_per_inch
92
85
  end
93
86
 
87
+ # Given at least two data points (barrel_length, muzzle_velocity)
88
+ # Estimate a muzzle velocity for an unknown length assuming the relationship
89
+ # between barrel length and muzzle velocity is linear
90
+ def self.estimate_mv(known_mvs, barrel_length, burn_length = nil)
91
+ if !known_mvs.is_a?(Hash) or known_mvs.empty?
92
+ raise(TypeError, "populated Hash expected")
93
+ end
94
+ if known_mvs.length == 1
95
+ return self.guess_mv(known_mvs.keys.first,
96
+ known_mvs.values.first,
97
+ burn_length,
98
+ barrel_length)
99
+ end
100
+ known_lengths = known_mvs.keys.sort
101
+ lb = known_lengths.first
102
+ ub = known_lengths.last
103
+ if lb < barrel_length and barrel_length < ub
104
+ # we can interpolate
105
+ known_lengths.each { |len|
106
+ if barrel_length < len
107
+ ub = len
108
+ break
109
+ end
110
+ lb = len
111
+ }
112
+ else
113
+ # we must extrapolate
114
+ if barrel_length < lb
115
+ lb, ub = known_lengths[0], known_lengths[1]
116
+ else
117
+ lb, ub = known_lengths[-2], known_lengths[-1]
118
+ end
119
+ end
120
+ lv, uv = known_mvs.fetch(lb), known_mvs.fetch(ub)
121
+
122
+ # TODO: consider adjusting m by burn_factor()
123
+ m, b = self.linear_coefficients(lb, ub, lv, uv)
124
+ m * barrel_length + b
125
+ end
126
+
127
+ # muzzle velocity curve is steeper below burn length and shallower above it
128
+ # the burn_factor can correct a linear prediction if we know the burn length
129
+ # take the average of the known and unknown
130
+ def self.burn_factor(burn_length, known_length, barrel_length)
131
+ return 1 unless burn_length
132
+ (burn_length.to_f / known_length + burn_length.to_f / barrel_length) / 2
133
+ end
134
+
135
+ # Assume: y = mx + b
136
+ # Given 2 points, return m and b
137
+ #
138
+ def self.linear_coefficients(x1, x2, y1, y2)
139
+ m = (y1 - y2) / (x1 - x2)
140
+ b = y1 - m * x1
141
+ [m, b]
142
+ end
143
+
94
144
  # Match and extract e.g. "16" from "16_inch_fps"
95
145
  BARREL_LENGTH_REGEX = /([0-9]+)_inch_fps/i
96
146
 
@@ -103,6 +153,7 @@ class Ballistics::Cartridge
103
153
  @yaml_data = hsh
104
154
  MANDATORY.each { |field, type|
105
155
  val = hsh.fetch(field)
156
+ val = val.to_s if field == "case" and type == :string
106
157
  Ballistics::YAML.check_type!(val, type)
107
158
  self.instance_variable_set("@#{field}", val)
108
159
  }
@@ -138,26 +189,12 @@ class Ballistics::Cartridge
138
189
 
139
190
  # estimate muzzle velocity for a given barrel length
140
191
  def mv(barrel_length, burn_length = nil)
192
+ # is MV already known?
141
193
  [barrel_length, barrel_length.floor, barrel_length.ceil].each { |candidate|
142
194
  mv = @muzzle_velocity[candidate]
143
195
  return mv if mv
144
196
  }
145
- burn_length ||= BURN_LENGTH.fetch(@case)
146
- known_lengths = @muzzle_velocity.keys
147
-
148
- case known_lengths.length
149
- when 0
150
- raise "no muzzle velocities available"
151
- when 1
152
- known_length = known_lengths.first
153
- self.class.guess_mv(known_length,
154
- @muzzle_velocity[known_length],
155
- burn_length,
156
- barrel_length)
157
- else
158
- # ok, now we need to interpolate if we can
159
- raise "not implemented yet"
160
- end
197
+ self.class.estimate_mv(@muzzle_velocity, barrel_length, BURN_LENGTH[@case])
161
198
  end
162
199
 
163
200
  def multiline
@@ -2,7 +2,7 @@
2
2
  # $id:
3
3
  # name:
4
4
  # case:
5
- # projectile: # ref to projectiles.yaml
5
+ # projectile: # ref to projectiles/$case.yaml
6
6
  # AT LEAST ONE OF
7
7
  # 16_inch_fps:
8
8
  # 20_inch_fps:
@@ -3,6 +3,8 @@ require 'ballistics/yaml'
3
3
  class Ballistics::Gun
4
4
  class ChamberNotFound < KeyError; end
5
5
 
6
+ YAML_DIR = 'guns'
7
+
6
8
  MANDATORY = {
7
9
  "name" => :string,
8
10
  "chamber" => :string,
@@ -25,29 +27,8 @@ class Ballistics::Gun
25
27
  # Load a built-in YAML file and instantiate gun objects
26
28
  # Return a hash of gun objects keyed by gun id (per the YAML)
27
29
  #
28
- def self.find(short_name)
29
- objects = {}
30
- Ballistics::YAML.load_built_in('guns', short_name).each { |id, hsh|
31
- obj = self.new(hsh)
32
- if block_given?
33
- objects[id] = obj if yield obj
34
- else
35
- objects[id] = obj
36
- end
37
- }
38
- objects
39
- end
40
-
41
- def self.find_id(id)
42
- Ballistics::YAML::BUILT_IN.fetch('guns').each { |short_name|
43
- object = self.find(short_name)[id]
44
- return object if object
45
- }
46
- nil
47
- end
48
-
49
- def self.fetch_id(id)
50
- self.find_id(id) or raise("id #{id} not found")
30
+ def self.find(file: nil, id: nil)
31
+ Ballistics::YAML.find(klass: self, file: file, id: id)
51
32
  end
52
33
 
53
34
  attr_reader(*MANDATORY.keys)
@@ -76,11 +57,11 @@ class Ballistics::Gun
76
57
  (hsh.keys - MANDATORY.keys - OPTIONAL.keys).each { |k|
77
58
  @extra[k] = hsh[k]
78
59
  }
79
- @cartridges = []
60
+ @cartridges = {}
80
61
  end
81
62
 
82
63
  def chamber=(new_chamber)
83
- @cartridges = []
64
+ @cartridges = {}
84
65
  @chamber = new_chamber
85
66
  end
86
67
 
@@ -15,11 +15,7 @@ class Ballistics::Problem
15
15
  }
16
16
 
17
17
  def self.simple(gun_id:, cart_id:, gun_family: nil)
18
- if gun_family
19
- gun = Ballistics::Gun.find(gun_family).fetch(gun_id)
20
- else
21
- gun = Ballistics::Gun.fetch_id(gun_id)
22
- end
18
+ gun = Ballistics::Gun.find(file: gun_family, id: gun_id)
23
19
  cart = gun.cartridges.fetch(cart_id)
24
20
  self.new(projectile: cart.projectile,
25
21
  cartridge: cart,
@@ -1,6 +1,8 @@
1
1
  require 'ballistics/yaml'
2
2
 
3
3
  class Ballistics::Projectile
4
+ YAML_DIR = 'projectiles'
5
+
4
6
  MANDATORY = {
5
7
  "name" => :string,
6
8
  "cal" => :float,
@@ -29,17 +31,8 @@ class Ballistics::Projectile
29
31
  # Load a built-in YAML file and instantiate projectile objects
30
32
  # Return a hash of projectile objects keyed by projectile id (per the YAML)
31
33
  #
32
- def self.find(short_name)
33
- objects = {}
34
- Ballistics::YAML.load_built_in('projectiles', short_name).each { |id, hsh|
35
- obj = self.new(hsh)
36
- if block_given?
37
- objects[id] = obj if yield obj
38
- else
39
- objects[id] = obj
40
- end
41
- }
42
- objects
34
+ def self.find(file: nil, id: nil)
35
+ Ballistics::YAML.find(klass: self, file: file, id: id)
43
36
  end
44
37
 
45
38
  # Normalize common flat-base and boat-tail terms to flat or boat
@@ -82,7 +75,9 @@ class Ballistics::Projectile
82
75
  BALLISTIC_COEFFICIENT.each { |field, type|
83
76
  if hsh.key?(field)
84
77
  val = hsh[field]
85
- Ballistics::YAML.check_type!(val, type)
78
+ if !Ballistics::YAML.check_type?(val, type)
79
+ raise(TypeError, "#{val} (#{field}) is not #{type}")
80
+ end
86
81
  self.instance_variable_set("@#{field}", val)
87
82
  @ballistic_coefficient[field] = val
88
83
  end
@@ -92,6 +87,7 @@ class Ballistics::Projectile
92
87
  OPTIONAL.each { |field, type|
93
88
  if hsh.key?(field)
94
89
  val = hsh[field]
90
+ val = val.to_s if field == "intended" and type == :string
95
91
  Ballistics::YAML.check_type!(val, type)
96
92
  if field == "base"
97
93
  @base = self.class.base(val)
@@ -34,6 +34,32 @@ module Ballistics::YAML
34
34
  end
35
35
  end
36
36
 
37
+ def self.find(klass:, file: nil, id: nil)
38
+ candidates = {}
39
+ objects = {}
40
+ yd = klass::YAML_DIR or raise("no YAML_DIR for #{klass}")
41
+ if file
42
+ candidates = self.load_built_in(yd, file)
43
+ else
44
+ BUILT_IN.fetch(yd).each { |f|
45
+ candidates.merge!(self.load_built_in(yd, f))
46
+ }
47
+ end
48
+ if id
49
+ klass.new candidates.fetch id
50
+ else
51
+ candidates.each { |cid, hsh|
52
+ obj = klass.new hsh
53
+ if block_given?
54
+ objects[cid] = obj if yield obj
55
+ else
56
+ objects[cid] = obj
57
+ end
58
+ }
59
+ objects
60
+ end
61
+ end
62
+
37
63
  def self.check_type?(val, type)
38
64
  case type
39
65
  when :string, :reference
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ballistics-ng
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.1
4
+ version: 0.1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Hull
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-27 00:00:00.000000000 Z
11
+ date: 2017-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: buildar
@@ -54,7 +54,7 @@ files:
54
54
  - examples/table.rb
55
55
  - ext/ballistics/ext.c
56
56
  - ext/ballistics/extconf.rb
57
- - ext/ballistics/gnu_ballistics.h
57
+ - ext/ballistics/include/gnu_ballistics.h
58
58
  - lib/ballistics.rb
59
59
  - lib/ballistics/atmosphere.rb
60
60
  - lib/ballistics/cartridge.rb