Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start v3 implemention #18

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is a macos fragment and I don't think it belongs here.

Binary file not shown.
151 changes: 129 additions & 22 deletions matlab/CARFAC_Design.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
% limitations under the License.

function CF = CARFAC_Design(n_ears, fs, ...
CF_CAR_params, CF_AGC_params, CF_IHC_params, CF_IHC_keyword)
CF_CAR_params, CF_AGC_params, CF_IHC_params, CF_SYN_params, ...
CF_version_keyword)
% function CF = CARFAC_Design(n_ears, fs, ...
% CF_CAR_params, CF_AGC_params, CF_IHC_params, CF_IHC_keyword)
% CF_CAR_params, CF_AGC_params, CF_IHC_params, CF_version_keyword)
%
% This function designs the CARFAC (Cascade of Asymmetric Resonators with
% Fast-Acting Compression); that is, it take bundles of parameters and
Expand All @@ -32,8 +33,8 @@
% CF_AGC_params bundles all the automatic gain control parameters
% CF_IHC_params bundles all the inner hair cell parameters
% Indendent of how many of these are provided, if the last arg is a string
% it will be interpreted as an IHC_style keyword ('just_hwr' or 'one_cap';
% omit or 'two_cap' for default v2 two-cap IHC model.
% it will be interpreted as a CF_version_keyword ('just_hwr' or 'one_cap';
% omit or 'two_cap' for default v2 two-cap IHC model; or 'do_syn')
%
% See other functions for designing and characterizing the CARFAC:
% [naps, CF] = CARFAC_Run(CF, input_waves)
Expand Down Expand Up @@ -63,23 +64,30 @@
case 5
last_arg = CF_IHC_params;
case 6
last_arg = CF_IHC_keyword;
last_arg = CF_SYN_params;
case 7
last_arg = CF_version_keyword;
end


% Last arg being a keyword can be 'do_syn' or an IHC version.
if ischar(last_arg) % string is a character array, not a string array.
IHC_keyword = last_arg;
CF_version_keyword = last_arg;
num_args = nargin - 1;
else
IHC_keyword = 'two_cap'; % the CARFAC v2 default
CF_version_keyword = 'two_cap'; % The CARFAC v2 default
num_args = nargin;
end

% Now num_args does not count the version_keyword. Can be up to 6.

if num_args < 1
n_ears = 1; % if more than 1, make them identical channels;
% then modify the design if necessary for different reasons
end

if num_args < 2
fs = 22050;
fs = 22050; % Keeping this poor default in v3 to encourage users to always specify.
end

if num_args < 3
Expand Down Expand Up @@ -110,22 +118,27 @@
'AGC_mix_coeff', 0.5);
end

if num_args < 5
one_cap = 0; % bool; 1 for Allen model, 0 for new default two-cap
just_hwr = 0; % bool; 0 for normal/fancy IHC; 1 for HWR
switch IHC_keyword
case 'just_hwr'
just_hwr = 1; % bool; 0 for normal/fancy IHC; 1 for HWR
case 'one_cap'
one_cap = 1; % bool; 1 for Allen model, as text states we use
case 'two_cap'
% nothing to do; accept the default
otherwise
error('unknown IHC_keyword in CARFAC_Design')
end
one_cap = 0; % bool; 1 for Allen model, 0 for new default two-cap
just_hwr = 0; % bool; 0 for normal/fancy IHC; 1 for HWR
do_syn = 0;
switch CF_version_keyword
case 'just_hwr'
just_hwr = 1; % bool; 0 for normal/fancy IHC; 1 for HWR
case 'one_cap'
one_cap = 1; % bool; 1 for Allen model, as text states we use
case 'do_syn';
do_syn = 1;
case 'two_cap'
% nothing to do; accept the v2 default, two-cap IHC, no SYN.
otherwise
error('unknown IHC_keyword in CARFAC_Design')
end

if num_args < 5 % Default IHC_params
CF_IHC_params = struct( ...
'just_hwr', just_hwr, ... % not just a simple HWR
'one_cap', one_cap, ... % bool; 0 for new two-cap hack
'do_syn', do_syn, ... % bool; 1 for v3 synapse feature
'tau_lpf', 0.000080, ... % 80 microseconds smoothing twice
'tau_out', 0.0005, ... % depletion tau is pretty fast
'tau_in', 0.010, ... % recovery tau is slower
Expand All @@ -135,6 +148,17 @@
'tau2_in', 0.010); % recovery tau is slower 10 ms
end

if num_args < 6 % Default the SYN_params.
n_classes = 3; % Default. Modify params and redesign to change.
% Parameters could generally have columns if channel-dependent.
CF_SYN_params = struct( ...
'do_syn', do_syn, ... % This may just turn it off completely.
'fs', fs, ...
'n_classes', n_classes, ...
'tau_lpf', 0.000080, ...
'reservoir_tau', 0.020);
end

% first figure out how many filter stages (PZFC/CARFAC channels):
pole_Hz = CF_CAR_params.first_pole_theta * fs / (2*pi);
n_ch = 0;
Expand All @@ -160,13 +184,15 @@
CAR_coeffs = CARFAC_DesignFilters(CF_CAR_params, fs, pole_freqs);
AGC_coeffs = CARFAC_DesignAGC(CF_AGC_params, fs, n_ch);
IHC_coeffs = CARFAC_DesignIHC(CF_IHC_params, fs, n_ch);
SYN_coeffs = CARFAC_DesignSynapses(CF_SYN_params, fs, pole_freqs);

% Copy same designed coeffs into each ear (can do differently in the
% future, e.g. for unmatched OHC_health).
for ear = 1:n_ears
ears(ear).CAR_coeffs = CAR_coeffs;
ears(ear).AGC_coeffs = AGC_coeffs;
ears(ear).IHC_coeffs = IHC_coeffs;
ears(ear).SYN_coeffs = SYN_coeffs;
end

CF = struct( ...
Expand All @@ -175,12 +201,14 @@
'CAR_params', CF_CAR_params, ...
'AGC_params', CF_AGC_params, ...
'IHC_params', CF_IHC_params, ...
'SYN_params', CF_SYN_params, ...
'n_ch', n_ch, ...
'pole_freqs', pole_freqs, ...
'ears', ears, ...
'n_ears', n_ears, ...
'open_loop', 0, ...
'linear_car', 0);
'linear_car', 0, ...
'do_syn', do_syn);


%% Design the filter coeffs:
Expand Down Expand Up @@ -539,3 +567,82 @@
'ihc_accum', 0);
end
end

%% the SYN design coeffs:
function SYN_coeffs = CARFAC_DesignSynapses(SYN_params, fs, pole_freqs)

if ~SYN_params.do_syn
SYN_coeffs = []; % Just return empty if we're not doing SYN.
return
end

n_ch = length(pole_freqs);
n_cl = SYN_params.n_classes;

sat_rates = 200;
switch n_cl % Just enough to test that both 2 and 3 can work.
case 2
spont_rates = [50, 5];
n_fibers = [50, 60];
agc_weights = [1.2, 2.4]'; % a column of weights
% Pick a sat level of reservoir state (move into params?):
w1 = 0.125 * [1, 1]; % How low the res gets in saturation.
case 3
spont_rates = [50, 6, 1];
n_fibers = [50, 35, 25];
agc_weights = [1.2, 1.2, 1.2]'; % a column of weights
% Pick a sat level of reservoir state (move into params?):
w1 = 0.2 * [1, 1, 1]; % How low the res gets in saturation.
otherwise
error('unimplemented n_classes in in SYN_params in CARFAC_DesignSynapses');
end
v_width = 0.02;
% Plural var names indicate values for the different rate classes.
v_widths = v_width * ones(1, n_cl);

% Do some design. First, gains to get sat_rate when sigmoid is 1, which
% involves the reservoir steady-state solution.
% Assume the sigmoid is switching between 0 and 1 at 50% duty cycle, so
% normalized value is 0.5.
% Most of these are not per-channel, but could be expanded that way
% later if desired.

% Mean sat prob of spike per sample per neuron, likely same for all
% classes.
% Use names 1 for sat and 0 for spont in some of these.
p1 = sat_rates / fs;
p0 = spont_rates / fs;

q1 = 1 - w1;
s1 = 0.5; % average sigmoid output at saturation
z1 = s1*w1;
% solve q1 = a1*z1 for gain coeff a1:
a1 = q1 ./ z1;
% solve p1 = a2*z1 for gain coeff a2:
a2 = p1 ./ z1;

% Now work out how to get the desired spont.
z0 = p0 ./ a2;
q0 = z0 .* a1;
w0 = 1 - q0;
s0 = z0 ./ w0;
% Solve for (negative) sigmoid midpoint offset that gets s0 right.
offsets = log((1 - s0)./s0);

spont_p = a2 .* w0 .* s0; % should match p0; check it; yes it does.

% Copy stuff needed at run time into coeffs.
SYN_coeffs = struct( ...
'n_ch', n_ch, ...
'n_classes', n_cl, ...
'n_fibers', ones(n_ch,1) * n_fibers, ... % Synaptopathy comes in here.
'v_widths', v_widths, ...
'v_halfs', offsets .* v_widths, ... % Same units as v_recep and v_widths.
'a1', a1, ... % Feedback gain
'a2', a2, ... % Output gain
'agc_weights', agc_weights, ... % try to make syn_out resemble ihc_out to go to agc_in.
'spont_p', spont_p, ...
'res_lpf_inits', q0, ...
'res_coeff', 1 - exp(-1/(SYN_params.reservoir_tau * fs)), ...
'lpf_coeff', 1 - exp(-1/(SYN_params.tau_lpf * fs)));

9 changes: 8 additions & 1 deletion matlab/CARFAC_IHC_Step.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
% See the License for the specific language governing permissions and
% limitations under the License.

function [ihc_out, state] = CARFAC_IHC_Step(bm_out, coeffs, state);
function [ihc_out, state, v_recep] = CARFAC_IHC_Step(bm_out, coeffs, state);
% function [ihc_out, state] = CARFAC_IHC_Step(bm_out, coeffs, state);
%
% One sample-time update of inner-hair-cell (IHC) model, including the
% detection nonlinearity and one or two capacitor state variables.
%
% receptor_potential output will be empty except in two_cap mode.
% Use it as input to CARFAC_SYN_Step to model synapses to get firing rates.

v_recep = []; % For cases other than two_cap and do_syn it's not used.
if coeffs.just_hwr
ihc_out = min(2, max(0, bm_out)); % limit it for stability
else
Expand Down Expand Up @@ -63,7 +67,10 @@
state.lpf1_state = state.lpf1_state + coeffs.lpf_coeff * ...
(ihc_out - state.lpf1_state);
ihc_out = state.lpf1_state - coeffs.rest_output;
% Return a modified receptor potential that's zero at rest, for SYN.
v_recep = coeffs.rest_cap1 - state.cap1_voltage;
end
end

% Leaving this for v2 cochleagram compatibility, but no v3/SYN version:
state.ihc_accum = state.ihc_accum + ihc_out; % for where decimated output is useful
11 changes: 11 additions & 0 deletions matlab/CARFAC_Init.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
CF.ears(ear).CAR_state = CAR_Init_State(CF.ears(ear).CAR_coeffs);
CF.ears(ear).IHC_state = IHC_Init_State(CF.ears(ear).IHC_coeffs);
CF.ears(ear).AGC_state = AGC_Init_State(CF.ears(ear).AGC_coeffs);
if CF.do_syn
CF.ears(ear).SYN_state = SYN_Init_State(CF.ears(ear).SYN_coeffs);
end
end


Expand Down Expand Up @@ -80,3 +83,11 @@
);
end
end

function state = SYN_Init_State(coeffs)
n_ch = coeffs.n_ch;
n_cl = coeffs.n_classes;
state = struct( ...
'reservoirs', ones(n_ch, 1) * coeffs.res_lpf_inits, ... % 0 full, 1 empty.
'lpf_state', ones(n_ch, 1) * coeffs.spont_p);

23 changes: 19 additions & 4 deletions matlab/CARFAC_Run_Segment.m
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,30 @@
input_waves(k, ear), CF.ears(ear).CAR_coeffs, CF.ears(ear).CAR_state);

% update IHC state & output on every time step, too
[ihc_out, CF.ears(ear).IHC_state] = CARFAC_IHC_Step( ...
[ihc_out, CF.ears(ear).IHC_state, v_recep] = CARFAC_IHC_Step( ...
car_out, CF.ears(ear).IHC_coeffs, CF.ears(ear).IHC_state);

% run the AGC update step, decimating internally,
if CF.do_syn
% Use v_recep from IHC_Step to
% update the SYNapse state and get firings and new nap.
[syn_out, firings, CF.ears(ear).SYN_state] = CARFAC_SYN_Step( ...
v_recep, CF.ears(ear).SYN_coeffs, CF.ears(ear).SYN_state);
% Use sum over syn_outs classes, appropriately scaled, as nap to agc.
% firings always positive, unless v2 ihc_out.
% syn_out can go a little negative; should be zero at rest.
nap = syn_out;
% Maybe still should add a way to return firings (of the classes).
else
% v2, ihc_out already has rest_output subtracted.
nap = ihc_out; % If no SYN, ihc_out goes to nap and agc as in v2.
end

% Use nap to run the AGC update step, decimating internally,
[CF.ears(ear).AGC_state, AGC_updated] = CARFAC_AGC_Step( ...
ihc_out, CF.ears(ear).AGC_coeffs, CF.ears(ear).AGC_state);
nap, CF.ears(ear).AGC_coeffs, CF.ears(ear).AGC_state);

% save some output data:
naps(k, :, ear) = ihc_out; % output to neural activity pattern
naps(k, :, ear) = nap; % output to neural activity pattern
if do_BM
BM(k, :, ear) = car_out;
state = CF.ears(ear).CAR_state;
Expand Down
26 changes: 26 additions & 0 deletions matlab/CARFAC_SYN_Step.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function [syn_out, firings, state] = CARFAC_SYN_Step(v_recep, coeffs, state)
% Drive multiple synapse classes with receptor potential from IHC,
% returning instantaneous spike rates per class, for a group of neurons
% associated with the CF channel, including reductions due to synaptopathy.

% Normalized offset position into neurotransmitter release sigmoid.
x = (v_recep - coeffs.v_halfs) ./ coeffs.v_widths ;

% This sigmoidal release_rates is relative to a max of 1, usually way lower.
s = 1 ./ (1 + exp(-x));
release_rates = (1 - state.reservoirs) .* s; % z = w*s

% Smooth once with LPF (receptor potential was already smooth):
state.lpf_state = state.lpf_state + coeffs.lpf_coeff * ...
(coeffs.a2 .* release_rates - state.lpf_state); % this is firing probs.
firing_probs = state.lpf_state; % Poisson rate per neuron per sample.
% Include number of effective neurons per channel here, and interval T;
% so the rates (instantaneous action potentials per second) can be huge.
firings = coeffs.n_fibers .* firing_probs;

% Feedback, to update reservoir state q for next time.
state.reservoirs = state.reservoirs + coeffs.res_coeff .* ...
(coeffs.a1 .* release_rates - state.reservoirs);

% Make an output that resembles ihc_out, to go to agc_in.
syn_out = (coeffs.n_fibers .* (firing_probs - coeffs.spont_p)) * coeffs.agc_weights;
Loading