ballistics-ng 0.1.0.1 → 0.1.1.1

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,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